unit main;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, Menus, ComCtrls,
  ExtCtrls, StdCtrls, lazutf8, DB, RTTICtrls, SynEdit, IpHtml, BCListBox,
  BGRACustomDrawn, BGRATheme, BCExpandPanels, Types, RichMemo, JvPanel,
  JvPageList, JvTabBar, JvNavigationPane, JvStrToHtml, SQLDB, SQLite3Conn,
  LCLType, lazfileutils, HtmlView, MarkdownProcessor, SpkToolbar, spkt_Tab,
  spkt_Pane, spkt_Buttons, lclintf, inifiles, SynEditTypes,
  about, notebook, code_note, options, notebook_property;

type
  TDataRecord = record
    ID: integer;
    ParentID: integer;
    Text: string;
  end;

type
  TNoteType = (ntText, ntMarkdown, ntCode, ntRich);

type

  { Tform_main }

  Tform_main = class(TForm)
    color_dialog: TColorDialog;
    edit_search: TEdit;
    find_dialog: TFindDialog;
    image_list_rich: TImageList;
    image_list_toolbar: TImageList;
    image_list: TImageList;
    btn_new_note: TJvNavPanelButton;
    mn_notebook_property: TMenuItem;
    replace_dialog: TReplaceDialog;
    Separator11: TMenuItem;
    mn_rich_undo: TMenuItem;
    mn_rich_redo: TMenuItem;
    mn_rich_paste: TMenuItem;
    mn_rich_delete: TMenuItem;
    mn_rich_selectall: TMenuItem;
    Separator10: TMenuItem;
    mn_rich_cut: TMenuItem;
    mn_rich_copy: TMenuItem;
    Separator9: TMenuItem;
    mn_syn_basic: TMenuItem;
    mn_syn_bat: TMenuItem;
    mn_syn_diff: TMenuItem;
    mn_syn_freepascal: TMenuItem;
    mn_syn_html: TMenuItem;
    mn_syn_ini: TMenuItem;
    mn_syn_java: TMenuItem;
    mn_syn_jscript: TMenuItem;
    mn_syn_pascal: TMenuItem;
    mn_syn_perl: TMenuItem;
    mn_syn_php: TMenuItem;
    mn_syn_python: TMenuItem;
    mn_syn_cpp: TMenuItem;
    mn_syn_sql: TMenuItem;
    mn_syn_xml: TMenuItem;
    mn_syn_css: TMenuItem;
    mn_note_move: TMenuItem;
    mn_note_copy: TMenuItem;
    menu_syn: TPopupMenu;
    menu_rich: TPopupMenu;
    Separator8: TMenuItem;
    mn_notebook_set_default: TMenuItem;
    import_dialog: TOpenDialog;
    Separator7: TMenuItem;
    nav_panel_style_manager: TJvNavPaneStyleManager;
    mn_file_option: TMenuItem;
    MenuItem10: TMenuItem;
    MenuItem11: TMenuItem;
    MenuItem12: TMenuItem;
    MenuItem13: TMenuItem;
    MenuItem14: TMenuItem;
    MenuItem15: TMenuItem;
    MenuItem16: TMenuItem;
    MenuItem17: TMenuItem;
    MenuItem18: TMenuItem;
    MenuItem19: TMenuItem;
    mn_about: TMenuItem;
    mn_md_head1: TMenuItem;
    mn_md_head2: TMenuItem;
    mn_md_head3: TMenuItem;
    mn_md_head4: TMenuItem;
    mn_md_head5: TMenuItem;
    mn_md_head6: TMenuItem;
    mn_visit_website: TMenuItem;
    mn_visit_boolnote: TMenuItem;
    MenuItem5: TMenuItem;
    MenuItem6: TMenuItem;
    MenuItem7: TMenuItem;
    MenuItem8: TMenuItem;
    MenuItem9: TMenuItem;
    mn_note_open: TMenuItem;
    mn_note_rename: TMenuItem;
    mn_note_delete: TMenuItem;
    menu_font_size: TPopupMenu;
    menu_head: TPopupMenu;
    Separator6: TMenuItem;
    mn_tab_close_current: TMenuItem;
    mn_tab_close_other: TMenuItem;
    mn_tab_close_all: TMenuItem;
    menu_note: TPopupMenu;
    Separator5: TMenuItem;
    mn_exit: TMenuItem;
    menu_font_name: TPopupMenu;
    mn_tab_ops: TPopupMenu;
    Separator2: TMenuItem;
    Separator1: TMenuItem;
    mn_notebook_delete: TMenuItem;
    mn_notebook_refresh: TMenuItem;
    menu_main: TPopupMenu;
    Separator4: TMenuItem;
    mn_notebook_new: TMenuItem;
    mn_notebook_rename: TMenuItem;
    Separator3: TMenuItem;
    nav_page_list: TJvPageList;
    JvPanel1: TJvPanel;
    JvPanel2: TJvPanel;
    nav_tab_bar: TJvTabBar;
    lb_notes: TListBox;
    mn_new_code_note: TMenuItem;
    mn_new_rich_note: TMenuItem;
    mn_new_text_note: TMenuItem;
    mn_new_markdown_note: TMenuItem;
    pnl_notebook: TJvPanel;
    menu_new_note: TPopupMenu;
    menu_notebook: TPopupMenu;
    SpkPane1: TSpkPane;
    SpkPane11: TSpkPane;
    SpkPane12: TSpkPane;
    spk_btn_code_find: TSpkSmallButton;
    spk_btn_code_replace: TSpkSmallButton;
    spk_btn_rich_find: TSpkSmallButton;
    spk_btn_rich_replace: TSpkSmallButton;
    spk_btn_md_find: TSpkSmallButton;
    spk_btn_md_replace: TSpkSmallButton;
    spk_btn_rich_numering_number1: TSpkSmallButton;
    spk_btn_rich_numering_none1: TSpkSmallButton;
    spk_btn_rich_numering_bullet1: TSpkSmallButton;
    spk_btn_code_paste: TSpkLargeButton;
    spk_btn_code_syn: TSpkLargeButton;
    SpkPane10: TSpkPane;
    SpkPane8: TSpkPane;
    SpkPane9: TSpkPane;
    spk_btn_code_cut: TSpkSmallButton;
    spk_btn_code_copy: TSpkSmallButton;
    spk_btn_code_undo: TSpkSmallButton;
    spk_btn_code_redo: TSpkSmallButton;
    spk_tab_code: TSpkTab;
    spk_btn_md_paste: TSpkLargeButton;
    SpkPane7: TSpkPane;
    spk_btn_md_cut: TSpkSmallButton;
    spk_btn_md_copy: TSpkSmallButton;
    spk_btn_rich_paste: TSpkLargeButton;
    SpkPane6: TSpkPane;
    spk_btn_rich_cut: TSpkSmallButton;
    spk_btn_rich_copy: TSpkSmallButton;
    spk_btn_file_import: TSpkLargeButton;
    spk_btn_file_create_markdown: TSpkLargeButton;
    spk_btn_file_create_code: TSpkLargeButton;
    spk_btn_file_create_rich: TSpkLargeButton;
    SpkPane2: TSpkPane;
    SpkPane3: TSpkPane;
    SpkPane4: TSpkPane;
    SpkPane5: TSpkPane;
    spk_btn_md_superscript: TSpkSmallButton;
    spk_btn_md_image: TSpkSmallButton;
    spk_btn_md_code_line: TSpkSmallButton;
    spk_btn_md_code_block: TSpkSmallButton;
    spk_btn_md_subscript: TSpkSmallButton;
    spk_btn_md_unorderedlist: TSpkSmallButton;
    spk_btn_md_orderedlist: TSpkSmallButton;
    spk_btn_md_todo: TSpkSmallButton;
    spk_btn_md_line: TSpkSmallButton;
    spk_btn_md_quote: TSpkSmallButton;
    spk_btn_md_hyperlink: TSpkSmallButton;
    spk_btn_md_table: TSpkSmallButton;
    spk_btn_md_head: TSpkSmallButton;
    spk_btn_md_bold: TSpkSmallButton;
    spk_btn_md_italic: TSpkSmallButton;
    spk_btn_md_strikeout: TSpkSmallButton;
    spk_tab_markdown: TSpkTab;
    spk_btn_rich_align_left: TSpkSmallButton;
    spk_btn_rich_align_center: TSpkSmallButton;
    spk_btn_rich_align_right: TSpkSmallButton;
    spk_btn_rich_align_justify: TSpkSmallButton;
    spk_panel_para: TSpkPane;
    spk_btn_rich_back_color: TSpkSmallButton;
    spk_btn_rich_strikeout: TSpkSmallButton;
    spk_btn_rich_font_color: TSpkSmallButton;
    spk_btn_rich_underline: TSpkSmallButton;
    spk_btn_rich_italic: TSpkSmallButton;
    spk_btn_rich_bold: TSpkSmallButton;
    spk_panel_font: TSpkPane;
    spk_combo_font_name: TSpkSmallButton;
    spk_combo_font_size: TSpkSmallButton;
    spk_tab_rich: TSpkTab;
    spk_btn_file_create_text: TSpkLargeButton;
    spk_panel_common: TSpkPane;
    spk_tab_note: TSpkTab;
    spk_toolbar: TSpkToolbar;
    Splitter1: TSplitter;
    Splitter2: TSplitter;
    sqlite3_connection: TSQLite3Connection;
    sql_transaction: TSQLTransaction;
    sql_query_notebook: TSQLQuery;
    status_bar: TStatusBar;
    tv_notebook: TTreeView;
    procedure edit_searchKeyPress(Sender: TObject; var Key: char);
    procedure find_dialogFind(Sender: TObject);
    procedure FormClick(Sender: TObject);
    procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
    procedure FormCloseQuery(Sender: TObject; var CanClose: boolean);
    procedure FormCreate(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure lb_notesClick(Sender: TObject);
    procedure lb_notesContextPopup(Sender: TObject; MousePos: TPoint;
      var Handled: boolean);
    procedure lb_notesDrawItem(Control: TWinControl; Index: integer;
      ARect: TRect; State: TOwnerDrawState);
    procedure mn_aboutClick(Sender: TObject);
    procedure mn_exitClick(Sender: TObject);
    procedure mn_file_optionClick(Sender: TObject);
    procedure mn_md_head1Click(Sender: TObject);
    procedure mn_md_head2Click(Sender: TObject);
    procedure mn_md_head3Click(Sender: TObject);
    procedure mn_md_head4Click(Sender: TObject);
    procedure mn_md_head5Click(Sender: TObject);
    procedure mn_md_head6Click(Sender: TObject);
    procedure mn_new_code_noteClick(Sender: TObject);
    procedure mn_new_markdown_noteClick(Sender: TObject);
    procedure mn_new_rich_noteClick(Sender: TObject);
    procedure mn_new_text_noteClick(Sender: TObject);
    procedure mn_notebook_deleteClick(Sender: TObject);
    procedure mn_notebook_newClick(Sender: TObject);
    procedure mn_notebook_propertyClick(Sender: TObject);
    procedure mn_notebook_refreshClick(Sender: TObject);
    procedure mn_notebook_renameClick(Sender: TObject);
    procedure mn_notebook_set_defaultClick(Sender: TObject);
    procedure mn_note_copyClick(Sender: TObject);
    procedure mn_note_deleteClick(Sender: TObject);
    procedure mn_note_moveClick(Sender: TObject);
    procedure mn_note_openClick(Sender: TObject);
    procedure mn_note_renameClick(Sender: TObject);
    procedure mn_rich_copyClick(Sender: TObject);
    procedure mn_rich_cutClick(Sender: TObject);
    procedure mn_rich_deleteClick(Sender: TObject);
    procedure mn_rich_pasteClick(Sender: TObject);
    procedure mn_rich_redoClick(Sender: TObject);
    procedure mn_rich_selectallClick(Sender: TObject);
    procedure mn_rich_undoClick(Sender: TObject);
    procedure mn_tab_close_allClick(Sender: TObject);
    procedure mn_tab_close_currentClick(Sender: TObject);
    procedure mn_tab_close_otherClick(Sender: TObject);
    procedure mn_visit_boolnoteClick(Sender: TObject);
    procedure mn_visit_websiteClick(Sender: TObject);
    procedure nav_page_listMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: integer);
    procedure nav_tab_barTabCloseQuery(Sender: TObject; Item: TJvTabBarItem;
      var CanClose: boolean);
    procedure nav_tab_barTabSelected(Sender: TObject; Item: TJvTabBarItem);
    procedure replace_dialogReplace(Sender: TObject);
    procedure spk_btn_code_copyClick(Sender: TObject);
    procedure spk_btn_code_cutClick(Sender: TObject);
    procedure spk_btn_code_findClick(Sender: TObject);
    procedure spk_btn_code_pasteClick(Sender: TObject);
    procedure spk_btn_code_redoClick(Sender: TObject);
    procedure spk_btn_code_replaceClick(Sender: TObject);
    procedure spk_btn_code_undoClick(Sender: TObject);
    procedure spk_btn_file_create_codeClick(Sender: TObject);
    procedure spk_btn_file_create_markdownClick(Sender: TObject);
    procedure spk_btn_file_create_richClick(Sender: TObject);
    procedure spk_btn_file_create_textClick(Sender: TObject);
    procedure spk_btn_file_importClick(Sender: TObject);
    procedure spk_btn_md_boldClick(Sender: TObject);
    procedure spk_btn_md_code_blockClick(Sender: TObject);
    procedure spk_btn_md_code_lineClick(Sender: TObject);
    procedure spk_btn_md_copyClick(Sender: TObject);
    procedure spk_btn_md_cutClick(Sender: TObject);
    procedure spk_btn_md_findClick(Sender: TObject);
    procedure spk_btn_md_hyperlinkClick(Sender: TObject);
    procedure spk_btn_md_imageClick(Sender: TObject);
    procedure spk_btn_md_italicClick(Sender: TObject);
    procedure spk_btn_md_lineClick(Sender: TObject);
    procedure spk_btn_md_orderedlistClick(Sender: TObject);
    procedure spk_btn_md_pasteClick(Sender: TObject);
    procedure spk_btn_md_quoteClick(Sender: TObject);
    procedure spk_btn_md_replaceClick(Sender: TObject);
    procedure spk_btn_md_strikeoutClick(Sender: TObject);
    procedure spk_btn_md_subscriptClick(Sender: TObject);
    procedure spk_btn_md_superscriptClick(Sender: TObject);
    procedure spk_btn_md_tableClick(Sender: TObject);
    procedure spk_btn_md_todoClick(Sender: TObject);
    procedure spk_btn_md_unorderedlistClick(Sender: TObject);
    procedure spk_btn_rich_align_centerClick(Sender: TObject);
    procedure spk_btn_rich_align_justifyClick(Sender: TObject);
    procedure spk_btn_rich_align_leftClick(Sender: TObject);
    procedure spk_btn_rich_align_rightClick(Sender: TObject);
    procedure spk_btn_rich_back_colorClick(Sender: TObject);
    procedure spk_btn_rich_boldClick(Sender: TObject);
    procedure spk_btn_rich_copyClick(Sender: TObject);
    procedure spk_btn_rich_cutClick(Sender: TObject);
    procedure spk_btn_rich_findClick(Sender: TObject);
    procedure spk_btn_rich_font_colorClick(Sender: TObject);
    procedure spk_btn_rich_italicClick(Sender: TObject);
    procedure spk_btn_rich_numering_bulletClick(Sender: TObject);
    procedure spk_btn_rich_numering_noneClick(Sender: TObject);
    procedure spk_btn_rich_numering_numberClick(Sender: TObject);
    procedure spk_btn_rich_pasteClick(Sender: TObject);
    procedure spk_btn_rich_replaceClick(Sender: TObject);
    procedure spk_btn_rich_strikeoutClick(Sender: TObject);
    procedure spk_btn_rich_underlineClick(Sender: TObject);
    procedure tv_notebookChange(Sender: TObject; Node: TTreeNode);
    procedure tv_notebookCustomDraw(Sender: TCustomTreeView;
      const ARect: TRect; var DefaultDraw: boolean);

    // 自定义事件
    procedure OnRichEditorFontNameClick(Sender: TObject);
    procedure OnRichEditorFontSizeClick(Sender: TObject);
    procedure OnRichMemoSelectionChange(Sender: TObject);
    procedure OnMarkdownEditorChange(Sender: TObject);
    procedure OnRichMemoMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: integer);
    procedure OnRichMemoContextPopup(Sender: TObject; MousePos: TPoint;
      var Handled: boolean);
    procedure OnCodeEditorSynClick(Sender: TObject);
  private
    // 笔记本相关操作
    procedure RefreshNotebook;
    procedure CreateNotebook;
    procedure RenameNotebook;
    procedure DeleteNotebook;

    // 笔记相关操作
    procedure RefreshNoteList;
    procedure SearchNote(str_keyword: string);

    procedure CreateTextNote;
    procedure CreateMarkdownNote;
    procedure CreateCodeNote;
    procedure CreateRichNote;

    procedure OpenNote(note_id: integer);
    procedure OpenTextEditor(str_title, str_content: string;
      parent_control: TWinControl);
    procedure OpenMarkdownEditor(str_title, str_content: string;
      parent_control: TWinControl);
    procedure OpenCodeEditor(str_title, str_content: string;
      parent_control: TWinControl);
    procedure OpenRichEditor(str_title, str_content: string;
      parent_control: TWinControl);

    function GetActiveTitleEdit(): TEdit;
    function GetActiveEditor(): TComponent;

    procedure RenameNote(note_id: integer; new_note_title: string);
    procedure DeleteNote(note_id: integer);

    procedure MoveNote(note_id, new_notebook_id: integer);
    procedure CopyNote(note_id, new_notebook_id: integer);

    procedure SaveNote(note_id, notebook_id: integer; note_type: TNoteType;
      note_title, note_content: string);
    procedure AddNote(notebook_id: integer; note_type: TNoteType;
      note_title, note_content: string);
    procedure UpdateNote(note_id: integer; note_title, note_content: string);

    // 导入操作
    procedure ImportFile(filename: string);

    //富文本笔记相关操作
    procedure SetRichEditorFontName(edit_text: TRichMemo; font_name: string);
    procedure SetRichEditorFontSize(edit_text: TRichMemo; font_size: integer);
    procedure SetRichEditorFontStyle(edit_text: TRichMemo;
      related_control: TComponent; font_style: TFontStyle);
    procedure SetRichEditorAlignment(edit_text: TRichMemo; alignment: TParaAlignment);
    procedure SetRichEditorParaNumbering(edit_text: TRichMemo;
      list_style: TParaNumStyle);

    // Markdown笔记相关操作
    procedure RenderMarkdown(edit_text: TMemo);
    procedure InsertMarkdownElement(edit_text: TMemo; str_element: string);
    procedure InsertMarkdownElementAhead(edit_text: TMemo; str_element: string);

    // 动作封装
    procedure ActionOpenNote;
    procedure ActionRenameNote;
    procedure ActionDeleteNote;
    procedure ActionMoveNote;
    procedure ActionCopyNote;

    // 其它
    procedure SelectDefaultNotebook;
    procedure ReleaseNoteListObjects;
    function FindNotebookNodeByNotebookId(notebook_id: integer): TTreeNode;
    function FindAllChildNodes(ANode: TTreeNode; notebook_id: integer): TTreeNode;
    function FindTabByNoteId(note_id: integer): TJvTabBarItem;
    function GetItemIndexByCursorForListBox(list_box: TListBox): integer;
    function ReadNoteFontConfig(note_type: TNoteType): TFontInfo;
    function CalcDataFileSize(): string;
    function GetCursorLine(Memo: TMemo): integer;
  public

  end;

var
  form_main: Tform_main;

implementation

{$R *.lfm}

{ Tform_main }

function Tform_main.ReadNoteFontConfig(note_type: TNoteType): TFontInfo;
var
  ini: TIniFile;
  note_type_array: array [0..3] of string = ('text', 'markdown', 'code', 'rich');
  font_name: string;
  font_size: integer;
  font_info: TFontInfo;
begin
  ini := TIniFile.Create(ExtractFilePath(ParamStr(0)) + 'config.ini');
  try
    font_name := ini.ReadString('editor', Format('%s_font_name',
      [note_type_array[integer(note_type)]]), '宋体');
    font_size := ini.ReadInteger('editor', Format('%s_font_size',
      [note_type_array[integer(note_type)]]), 9);
    font_info.FontName := font_name;
    font_info.FontSize := font_size;
  finally
    ini.Free;
  end;
  Result := font_info;
end;

function Tform_main.CalcDataFileSize(): string;
const
  KB = 1024;
  MB = 1024 * KB;
  GB = 1024 * MB;
var
  data_file_path: string;
  file_size: int64;
  Size: double;
begin
  data_file_path := ExtractFilePath(ParamStr(0)) + '/data/note.db';
  file_size := FileSizeUtf8(data_file_path);

  if file_size < KB then
  begin
    Result := Format('%d bytes', [file_size]);
  end
  else if file_size < MB then
  begin
    Size := file_size / KB;
    Result := Format('%.2f KB', [Size]);
  end
  else if file_size < GB then
  begin
    Size := file_size / MB;
    Result := Format('%.2f MB', [Size]);
  end
  else
  begin
    Size := file_size / GB;
    Result := Format('%.2f GB', [Size]);
  end;
end;

procedure Tform_main.RefreshNotebook;
var
  sql: string;
  ANode, ParentNode: TTreeNode;
begin
  sql := 'WITH RecursiveNotebook AS (' + ' SELECT id, parent, name ' +
    ' FROM notebook ' + ' WHERE parent IS NULL ' + ' UNION ALL ' +
    ' SELECT c.id, c.parent, c.name ' + ' FROM notebook c ' +
    ' INNER JOIN RecursiveNotebook rc ON c.parent = rc.id ' +
    ') SELECT * FROM RecursiveNotebook;';
  sql_query_notebook.Close;
  sql_query_notebook.SQL.Text := sql;
  sql_query_notebook.Open;


  tv_notebook.Items.Clear;
  while not sql_query_notebook.EOF do
  begin
    if sql_query_notebook.FieldByName('parent').IsNull then
      ParentNode := nil
    else
      ParentNode := tv_notebook.Items.FindNodeWithData(
        Pointer(sql_query_notebook.FieldByName('parent').AsInteger));

    ANode := tv_notebook.Items.AddChild(ParentNode,
      sql_query_notebook.FieldByName('name').AsString);
    ANode.Data := Pointer(sql_query_notebook.FieldByName('id').AsInteger);
    ANode.ImageIndex := 0;
    sql_query_notebook.Next;
  end;
end;

procedure Tform_main.RefreshNoteList;
var
  sql: string;
  selected_node: TTreeNode;
  notebook_id: integer;
  sql_query_notes: TSQLQuery;

  note_list_text: string;
  note_id: string;
  note_title: string;
  note_update_time: string;
  note_summary: string;
  item_data: TObject;
begin
  selected_node := tv_notebook.Selected;
  if selected_node = nil then
    notebook_id := -1
  else
    notebook_id := integer(selected_node.Data);

  sql_query_notes := TSQLQuery.Create(nil);
  try
    sql_query_notes.SQLConnection := sqlite3_connection;
    if notebook_id > 0 then
    begin
      sql := 'select id, notebook, note_type, title, content, favorite_flag, ' +
        'delete_flag, top_flag ,create_time, update_time from note ' +
        'where notebook = :notebook_id order by update_time desc;';
      sql_query_notes.SQL.Text := sql;
      sql_query_notes.ParamByName('notebook_id').AsInteger := notebook_id;
    end
    else
    begin
      sql := 'select id, notebook, note_type, title, content, favorite_flag, ' +
        'delete_flag, top_flag ,create_time, update_time from note order ' +
        'by update_time desc;';
      sql_query_notes.SQL.Text := sql;
    end;

    sql_query_notes.Open;
    self.lb_notes.Clear;
    sql_query_notes.First;
    while not sql_query_notes.EOF do
    begin
      note_title := sql_query_notes.FieldByName('title').AsString;
      note_update_time := sql_query_notes.FieldByName('update_time').AsString;
      note_summary := sql_query_notes.FieldByName('content').AsString;
      note_summary := note_summary.Substring(0, 100);
      note_list_text := Format('%s$%s$%s', [note_title, note_update_time,
        note_summary]);
      self.lb_notes.Items.Add(note_list_text);

      item_data := TStringList.Create;
      note_id := sql_query_notes.FieldByName('id').AsString;
      TStringList(item_data).Add(note_id);
      TStringList(item_data).Add(sql_query_notes.FieldByName('notebook').AsString);
      TStringList(item_data).Add(sql_query_notes.FieldByName('note_type').AsString);
      self.lb_notes.Items.Objects[self.lb_notes.Count - 1] := item_data;

      sql_query_notes.Next;
    end;
  finally
    sql_query_notes.Close;
    sql_query_notes.Free;
  end;
end;

procedure Tform_main.ReleaseNoteListObjects;
var
  i: integer;
begin
  for i := 0 to lb_notes.Items.Count - 1 do
  begin
    //TObject(lb_notes.Items.Objects[i]).Free;
    lb_notes.Items.Objects[i].Free;
  end;
  lb_notes.Clear;
end;

procedure Tform_main.SearchNote(str_keyword: string);
var
  sql: string;
  sql_query_notes: TSQLQuery;

  note_list_text: string;
  note_id: string;
  note_title: string;
  note_update_time: string;
  note_summary: string;
  item_data: TObject;
begin
  sql_query_notes := TSQLQuery.Create(nil);
  try
    sql_query_notes.SQLConnection := sqlite3_connection;
    sql := Format('select id, notebook, note_type, title, content, favorite_flag, ' +
      'delete_flag, top_flag ,create_time, update_time from note ' +
      'where title like "%%%s%%" or content like "%%%s%%" order ' +
      'by update_time desc;', [str_keyword, str_keyword]);
    sql_query_notes.SQL.Text := sql;
    //sql_query_notes.ParamByName('keyword').AsString := str_keyword;

    sql_query_notes.Open;
    self.lb_notes.Clear;
    sql_query_notes.First;
    while not sql_query_notes.EOF do
    begin
      note_title := sql_query_notes.FieldByName('title').AsString;
      note_update_time := sql_query_notes.FieldByName('update_time').AsString;
      note_summary := sql_query_notes.FieldByName('content').AsString;
      note_summary := note_summary.Substring(0, 100);
      note_list_text := Format('%s$%s$%s', [note_title, note_update_time,
        note_summary]);
      self.lb_notes.Items.Add(note_list_text);

      item_data := TStringList.Create;
      note_id := sql_query_notes.FieldByName('id').AsString;
      TStringList(item_data).Add(note_id);
      TStringList(item_data).Add(sql_query_notes.FieldByName('notebook').AsString);
      TStringList(item_data).Add(sql_query_notes.FieldByName('note_type').AsString);
      self.lb_notes.Items.Objects[self.lb_notes.Count - 1] := item_data;

      sql_query_notes.Next;
    end;
  finally
    sql_query_notes.Close;
    sql_query_notes.Free;
  end;
end;

procedure Tform_main.mn_exitClick(Sender: TObject);
begin
  Application.Terminate;
end;

procedure Tform_main.mn_file_optionClick(Sender: TObject);
var
  dlg_options: Tform_options;
begin
  dlg_options := Tform_options.Create(self);
  dlg_options.ShowModal;
end;

procedure Tform_main.mn_md_head1Click(Sender: TObject);
var
  edit_text: TMemo;
begin
  edit_text := self.GetActiveEditor() as TMemo;
  if edit_text = nil then
    exit;

  self.InsertMarkdownElementAhead(edit_text, '# ');
end;

procedure Tform_main.mn_md_head2Click(Sender: TObject);
var
  edit_text: TMemo;
begin
  edit_text := self.GetActiveEditor() as TMemo;
  if edit_text = nil then
    exit;

  self.InsertMarkdownElementAhead(edit_text, '## ');
end;

procedure Tform_main.mn_md_head3Click(Sender: TObject);
var
  edit_text: TMemo;
begin
  edit_text := self.GetActiveEditor() as TMemo;
  if edit_text = nil then
    exit;

  self.InsertMarkdownElementAhead(edit_text, '### ');
end;

procedure Tform_main.mn_md_head4Click(Sender: TObject);
var
  edit_text: TMemo;
begin
  edit_text := self.GetActiveEditor() as TMemo;
  if edit_text = nil then
    exit;

  self.InsertMarkdownElementAhead(edit_text, '#### ');
end;

procedure Tform_main.mn_md_head5Click(Sender: TObject);
var
  edit_text: TMemo;
begin
  edit_text := self.GetActiveEditor() as TMemo;
  if edit_text = nil then
    exit;

  self.InsertMarkdownElementAhead(edit_text, '##### ');
end;

procedure Tform_main.mn_md_head6Click(Sender: TObject);
var
  edit_text: TMemo;
begin
  edit_text := self.GetActiveEditor() as TMemo;
  if edit_text = nil then
    exit;

  self.InsertMarkdownElementAhead(edit_text, '###### ');
end;

procedure Tform_main.lb_notesDrawItem(Control: TWinControl; Index: integer;
  ARect: TRect; State: TOwnerDrawState);
var
  round_rect: TRect;
  item_text: string;
  item_list: TStringList;
  item_title: string;
  item_update_time: string;
  item_summary: string;

  item_data: TStringList;
  note_type: TNoteType;

  rect_summary: TRect;
  style: TTextStyle;
  note_icon: TBitmap;
begin
  with (Control as TListBox).Canvas do
  begin
    // 绘制列表项背景为灰色
    Brush.Color := RGBToColor(240, 240, 240);
    FillRect(ARect);

    // 绘制圆角背景
    if odSelected in State then
    begin
      Brush.Color := RGBToColor(242, 247, 255);
      Pen.Color := RGBToColor(71, 133, 251);
    end
    else if odHotLight in State then
    begin
      Brush.Color := RGBToColor(247, 250, 255);
      Pen.Color := RGBToColor(124, 170, 249);
    end
    else
    begin
      Brush.Color := RGBToColor(255, 255, 255);
      Pen.Color := RGBToColor(255, 255, 255);
    end;
    round_rect := Rect(ARect.Left + 5, ARect.Top + 5, ARect.Right - 5, ARect.Bottom - 5);
    RoundRect(round_rect, 5, 5);

    // 绘制图标
    item_data := TStringList((Control as TListBox).Items.Objects[Index]);
    if item_data <> nil then
    begin
      if item_data.Count > 0 then
        note_type := TNoteType(StrToInt(item_data[2]))
      else
        note_type := ntText;
    end
    else
      note_type := ntText;

    note_icon := TBitmap.Create;
    case note_type of
      ntText: self.image_list.GetBitmap(1, note_icon);
      ntMarkdown: self.image_list.GetBitmap(2, note_icon);
      ntCode: self.image_list.GetBitmap(3, note_icon);
      ntRich: self.image_list.GetBitmap(4, note_icon);
      else
        self.image_list.GetBitmap(1, note_icon);
    end;
    Draw(ARect.Left + 10, ARect.Top + 10, note_icon);

    // 绘制文字
    item_text := (Control as TListBox).Items[Index];
    item_list := TStringList.Create;
    try
      item_list.Delimiter := '$';
      item_list.StrictDelimiter := True;
      item_list.DelimitedText := item_text;
      item_title := item_list[0];
      item_update_time := item_list[1];
      item_summary := item_list[2];

      Font.Color := clBlack;
      Font.Size := 10;
      //TextOut(ARect.Left + 10, aRect.Top + 10, (Control as TListBox).Items[Index]);
      TextOut(ARect.Left + 30, aRect.Top + 10, item_title);

      Font.Color := RGBToColor(128, 128, 128);
      Font.Size := 7;
      TextOut(ARect.Left + 10, aRect.Top + 40, item_update_time);

      Font.Size := 9;
      //TextOut(ARect.Left + 10, aRect.Top + 60, item_summary);
      rect_summary := Rect(ARect.Left + 10, ARect.Top + 60, ARect.Right -
        10, ARect.Bottom - 10);
      style := TextStyle;
      style.Wordbreak := True;
      style.EndEllipsis := True;
      //TextStyle := style;
      TextRect(rect_summary, ARect.Left + 10, aRect.Top + 60, item_summary, style);
    finally
      item_list.Free;
      note_icon.Free;
    end;
  end;
end;

procedure Tform_main.mn_aboutClick(Sender: TObject);
var
  dlg_about: TForm_about;
begin
  dlg_about := TForm_about.Create(nil);
  dlg_about.ShowModal;
end;

procedure Tform_main.FormShow(Sender: TObject);
var
  app_path: string;
  data_file_size: string;
  ini: TIniFile;
  data_filename: string;
begin
  self.Position := poScreenCenter;

  spk_tab_rich.Visible := False;
  spk_tab_markdown.Visible := False;
  spk_tab_code.Visible := False;

  app_path := ExtractFilePath(ParamStr(0));
  app_path := ExtractFilePath(ParamStr(0));
  ini := TIniFile.Create(ExtractFilePath(ParamStr(0)) + 'config.ini');
  try
    data_filename := ini.ReadString('data_storage', 'data_file',
      Format('%sdata\note.db', [app_path]));
    if not FileExists(data_filename) then
      data_filename := Format('%s/data/note.db', [app_path]);
    sqlite3_connection.DatabaseName := data_filename;
    sqlite3_connection.Connected := True;

    self.RefreshNotebook;
    self.RefreshNoteList;

    self.SelectDefaultNotebook;

    data_file_size := self.CalcDataFileSize();
    status_bar.Panels[1].Text := Format('数据文件大小:%s', [data_file_size]);
  finally
    ini.Free;
  end;
end;

function Tform_main.GetItemIndexByCursorForListBox(list_box: TListBox): integer;
var
  P: TPoint;
  item_index: integer;
begin
  P := Mouse.CursorPos;
  P := list_box.ScreenToClient(P);
  item_index := lb_notes.ItemAtPos(P, True);
  if (item_index < 0) or (item_index > lb_notes.Items.Count - 1) then
    Result := -1;
  Result := item_index;
end;

procedure Tform_main.ActionOpenNote;
var
  item_data: TStringList;
  nav_data: TStringList;
  note_id: integer;
  spec_tab_item: TJvTabBarItem;
  i: integer;
  item_index: integer;
begin
  item_index := self.GetItemIndexByCursorForListBox(lb_notes);
  if item_index < 0 then
    exit;
  spec_tab_item := nil;
  item_data := TStringList(lb_notes.Items.Objects[item_index]);
  if item_data.Count <= 0 then
    exit;
  note_id := StrToInt(item_data[0]);

  //遍历打开的tab，查看该笔记是否已打开。
  //nav_data := TStringList.Create;
  //TStringList(nav_data).Add(IntToStr(note_id));
  //spec_tab_item := self.nav_tab_bar.FindData(nav_data);
  for i := 0 to self.nav_tab_bar.Tabs.Count - 1 do
  begin
    nav_data := TStringList(self.nav_tab_bar.Tabs[i].Data);
    if nav_data.Count > 0 then
    begin
      if nav_data[0] = IntToStr(note_id) then
      begin
        spec_tab_item := self.nav_tab_bar.Tabs[i];
        break;
      end;
    end;
  end;
  if spec_tab_item = nil then
  begin
    self.OpenNote(note_id);
    self.nav_tab_bar.Tabs[self.nav_tab_bar.Tabs.Count - 1].Selected := True;
    //self.nav_tab_bar.Tabs[self.nav_tab_bar.Tabs.Count -1].Data := nav_data;
  end
  else
    spec_tab_item.Selected := True;
end;

function Tform_main.FindTabByNoteId(note_id: integer): TJvTabBarItem;
var
  spec_tab_item: TJvTabBarItem;
  i: integer;
  nav_data: TStringList;
begin
  spec_tab_item := nil;

  for i := 0 to self.nav_tab_bar.Tabs.Count - 1 do
  begin
    nav_data := TStringList(self.nav_tab_bar.Tabs[i].Data);
    if nav_data.Count > 0 then
    begin
      if nav_data[0] = IntToStr(note_id) then
      begin
        spec_tab_item := self.nav_tab_bar.Tabs[i];
        break;
      end;
    end;
  end;
  Result := spec_tab_item;
end;

procedure Tform_main.ActionRenameNote;
var
  item_data: TStringList;
  note_id: integer;
  spec_tab_item: TJvTabBarItem;

  old_note_title: string;
  new_note_title: string;
  idx: integer;

  edit_title: TEdit;
  item_index: integer;
begin
  item_index := self.GetItemIndexByCursorForListBox(lb_notes);
  if item_index < 0 then
    exit;

  item_data := TStringList(lb_notes.Items.Objects[item_index]);
  if item_data = nil then
    exit;
  if item_data.Count <= 0 then
    exit;

  note_id := StrToInt(item_data[0]);
  old_note_title := lb_notes.Items[item_index];
  idx := Pos('$', old_note_title);
  old_note_title := old_note_title.Substring(0, idx - 1);
  new_note_title := InputBox('重命名笔记', '请输入新的笔记标题：',
    old_note_title);
  if new_note_title = '' then
    exit;

  // 确保如果该笔记若打开，则同样进行修改。
  spec_tab_item := self.FindTabByNoteId(note_id);
  if spec_tab_item <> nil then
  begin
    edit_title := self.nav_page_list.Pages[spec_tab_item.Index].FindComponent(
      'edit_title') as TEdit;
    if edit_title <> nil then
    begin
      edit_title.Text := new_note_title;
    end;
  end;

  self.RenameNote(note_id, new_note_title);
  self.RefreshNoteList;
end;

procedure Tform_main.ActionDeleteNote;
var
  item_data: TStringList;
  note_id: integer;
  spec_tab_item: TJvTabBarItem;

  old_note_title: string;
  idx: integer;

  item_index: integer;
begin
  item_index := self.GetItemIndexByCursorForListBox(lb_notes);
  if item_index < 0 then
    exit;

  item_data := TStringList(lb_notes.Items.Objects[item_index]);
  if item_data = nil then
    exit;
  if item_data.Count <= 0 then
    exit;

  note_id := StrToInt(item_data[0]);
  old_note_title := lb_notes.Items[item_index];
  idx := Pos('$', old_note_title);
  old_note_title := old_note_title.Substring(0, idx - 1);
  if MessageDlg('确认', Format('您是否要删除标题为(%s)的笔记?',
    [old_note_title]), mtConfirmation, [mbYes, mbNo], 0) = mrYes then
  begin
    // 若对应tab打开的话，先关闭。
    spec_tab_item := self.FindTabByNoteId(note_id);
    if spec_tab_item <> nil then
    begin
      self.nav_tab_bar.CloseTab(spec_tab_item);
    end;
    // 再运行删除操作。
    self.DeleteNote(note_id);
  end;
  self.RefreshNoteList;
end;

procedure Tform_main.lb_notesClick(Sender: TObject);
begin
  self.ActionOpenNote;
end;

procedure Tform_main.lb_notesContextPopup(Sender: TObject; MousePos: TPoint;
  var Handled: boolean);
var
  idx: integer;
  ClientMousePos: TPoint;
begin
  idx := lb_notes.ItemAtPos(MousePos, True);
  if idx >= 0 then
  begin
    ClientMousePos := lb_notes.ClientToScreen(MousePos);
    menu_note.PopUp(ClientMousePos.X, ClientMousePos.Y);
  end;
  Handled := True;
end;

procedure Tform_main.FormClick(Sender: TObject);
begin

end;

procedure Tform_main.FormClose(Sender: TObject; var CloseAction: TCloseAction);
begin
  sqlite3_connection.Connected := False;
  application.Terminate;
end;

procedure Tform_main.FormCloseQuery(Sender: TObject; var CanClose: boolean);
var
  i: integer;
begin
  // 关闭各个tab，以便确保笔记得以保存。
  for i := self.nav_tab_bar.Tabs.Count - 1 downto 0 do
  begin
    if self.nav_tab_bar.Tabs[i] <> nil then
    begin
      //self.nav_tab_bar.Tabs[i].Closing;
      self.nav_tab_bar.CloseTab(self.nav_tab_bar.Tabs[i]);
    end;
  end;

  // 释放笔记列表及对象
  self.ReleaseNoteListObjects;
end;

procedure Tform_main.edit_searchKeyPress(Sender: TObject; var Key: char);
begin
  if Key = char(13) then
  begin
    if self.Text <> '' then
      self.SearchNote(self.edit_search.Text)
    else
      self.RefreshNoteList;
  end;
end;

procedure Tform_main.find_dialogFind(Sender: TObject);
var
  edit_text: TComponent;
  i: integer;
  nav_data: TObject;
  note_type: TNoteType;
begin
  // 获取当前编辑器
  edit_text := self.GetActiveEditor();
  if edit_text = nil then
    exit;

  // 判断当前笔记类型
  nav_data := self.nav_tab_bar.SelectedTab.Data;
  if TStringList(nav_data).Count = 0 then
  begin
    ShowMessage('未获取到笔记信息。');
    exit;
  end;
  note_type := TNoteType(StrToInt(TStringList(nav_data)[2]));

  // 查找操作
  with Sender as TFindDialog do
  begin
    case note_type of
      ntText:
      begin
        i := UTF8Pos(FindText, TMemo(edit_text).Lines.Text);
        if i > 0 then
        begin
          TMemo(edit_text).SelStart := i - 1;
          TMemo(edit_text).SelLength := UTF8Length(FindText);
          TMemo(edit_text).SetFocus;
        end
        else
          Beep;
      end;
      ntMarkdown:
      begin
        i := UTF8Pos(FindText, TMemo(edit_text).Lines.Text);
        if i > 0 then
        begin
          TMemo(edit_text).SelStart := i - 1;
          TMemo(edit_text).SelLength := UTF8Length(FindText);
          TMemo(edit_text).SetFocus;
        end
        else
          Beep;
      end;
      ntCode:
      begin
        //i := UTF8Pos(FindText, TSynEdit(edit_text).Lines.Text);
        i := Pos(FindText, TSynEdit(edit_text).Lines.Text);
        if i > 0 then
        begin
          TSynEdit(edit_text).SelStart := i;
          //TSynEdit(edit_text).SelLength := UTF8Length(FindText);
          //TSynEdit(edit_text).SelText := FindText;
          TSynEdit(edit_text).SetFocus;
        end
        else
          Beep;
      end;
      ntRich:
      begin
        i := UTF8Pos(FindText, TRichMemo(edit_text).Lines.Text);
        if i > 0 then
        begin
          TRichMemo(edit_text).SelStart := i - 1;
          TRichMemo(edit_text).SelLength := UTF8Length(FindText);
          TRichMemo(edit_text).SetFocus;
        end
        else
          Beep;
      end;
    end;
  end;
end;

procedure Tform_main.FormCreate(Sender: TObject);
var
  font_name_item: TMenuItem;
  font_size_item: TMenuItem;
  i: integer;
begin
  self.Width := Round(Screen.Width * 3 / 4);
  self.Height := Round(Screen.Height * 3 / 4);

  // 富文本字体菜单
  for i := 0 to screen.Fonts.Count - 1 do
  begin
    font_name_item := TMenuItem.Create(self);
    font_name_item.Caption := screen.Fonts[i];
    menu_font_name.Items.Add(font_name_item);
    font_name_item.OnClick := @OnRichEditorFontNameClick;
  end;

  // 富文本字号菜单
  for i := 0 to menu_font_size.Items.Count - 1 do
  begin
    font_size_item := menu_font_size.Items[i];
    font_size_item.OnClick := @OnRichEditorFontSizeClick;
  end;

  // 代码笔记语法菜单
  for i := 0 to menu_syn.Items.Count - 1 do
  begin
    menu_syn.Items[i].OnClick := @OnCodeEditorSynClick;
  end;
end;

procedure Tform_main.CreateCodeNote;
var
  new_tabbar: TJvTabBarItem;
  new_page: TJvCustomPage;

  frm_code_note: Tfrm_code_note;

  notebook_id: integer;
  note_list_text: string;
  note_data: TObject;

  selected_node: TTreeNode;
  font_info: TFontInfo;
begin
  selected_node := tv_notebook.Selected;
  if selected_node = nil then
  begin
    ShowMessage('请先选择要保存的笔记本。');
    exit;
  end;
  notebook_id := integer(selected_node.Data);

  new_tabbar := self.nav_tab_bar.AddTab('新建代码笔记');
  new_tabbar.ImageIndex := 3;
  new_page := self.nav_page_list.Pages[new_tabbar.Index];
  new_tabbar.Selected := True;

  frm_code_note := Tfrm_code_note.Create(new_page);
  frm_code_note.Name := 'frm_code_note';
  frm_code_note.Parent := new_page;
  frm_code_note.Align := alClient;
  //frm_code_note.SetTitle('新建代码笔记');
  frm_code_note.SetContent('');

  font_info := self.ReadNoteFontConfig(ntCode);
  frm_code_note.SetDefaultFont(font_info.FontName, font_info.FontSize);

  self.nav_page_list.ActivePageIndex := new_page.PageIndex;
  note_list_text := Format('新建代码笔记$%s$%s',
    [FormatDateTime('yyyy-mm-dd', Now), '']);
  lb_notes.Items.Insert(0, note_list_text);

  note_data := TStringList.Create;
  TStringList(note_data).Add('0');
  TStringList(note_data).Add(IntToStr(notebook_id));
  TStringList(note_data).Add('2');
  new_tabbar.Data := note_data;

  spk_tab_rich.Visible := False;
  spk_tab_Markdown.Visible := False;
  spk_tab_code.Visible := True;
  spk_toolbar.TabIndex := 1;
end;

procedure Tform_main.mn_new_code_noteClick(Sender: TObject);
begin
  self.CreateCodeNote;
end;

procedure Tform_main.mn_new_markdown_noteClick(Sender: TObject);
begin
  self.CreateMarkdownNote;
end;

procedure Tform_main.OpenNote(note_id: integer);
var
  sql: string;
  sql_query_note: TSQLQuery;

  new_tabbar: TJvTabBarItem;
  new_tabbar_data: TObject;
  new_page: TJvCustomPage;
begin
  sql_query_note := TSQLQuery.Create(nil);
  try
    new_tabbar_data := TStringList.Create;
    sql_query_note.SQLConnection := sqlite3_connection;

    sql := 'select id, notebook, note_type, title, content, favorite_flag,  ' +
      ' delete_flag, top_flag, create_time, update_time from note where ' +
      ' id = :note_id;';
    sql_query_note.SQL.Text := sql;
    sql_query_note.ParamByName('note_id').AsInteger := note_id;
    sql_query_note.Open;

    sql_query_note.First;
    while not sql_query_note.EOF do
    begin
      new_tabbar := self.nav_tab_bar.AddTab(
        sql_query_note.FieldByName('title').AsString.Substring(0, 30));

      case TNoteType(sql_query_note.FieldByName('note_type').AsInteger) of
        ntText: new_tabbar.ImageIndex := 1;
        ntMarkdown: new_tabbar.ImageIndex := 2;
        ntCode: new_tabbar.ImageIndex := 3;
        ntRich: new_tabbar.ImageIndex := 4;
        else
          new_tabbar.ImageIndex := 1;
      end;
      new_tabbar.Hint := sql_query_note.FieldByName('title').AsString;
      TStringList(new_tabbar_data).Add(sql_query_note.FieldByName('id').AsString);
      TStringList(new_tabbar_data).Add(sql_query_note.FieldByName('notebook').AsString);
      TStringList(new_tabbar_data).Add(sql_query_note.FieldByName('note_type').AsString);
      new_tabbar.Data := new_tabbar_data;

      new_page := self.nav_page_list.Pages[new_tabbar.Index];

      case TNoteType(sql_query_note.FieldByName('note_type').AsInteger) of
        ntText: self.OpenTextEditor(sql_query_note.FieldByName('title').AsString,
            sql_query_note.FieldByName('content').AsString, new_page);
        ntMarkdown: self.OpenMarkdownEditor(sql_query_note.FieldByName('title').AsString,
            sql_query_note.FieldByName('content').AsString, new_page);
        ntCode: self.OpenCodeEditor(sql_query_note.FieldByName('title').AsString,
            sql_query_note.FieldByName('content').AsString, new_page);
        ntRich: self.OpenRichEditor(sql_query_note.FieldByName('title').AsString,
            sql_query_note.FieldByName('content').AsString, new_page);
        else
          self.OpenTextEditor(sql_query_note.FieldByName('title').AsString,
            sql_query_note.FieldByName('content').AsString, new_page);
      end;

      sql_query_note.Next;
    end;
  except
    on E: Exception do
    begin
      sql_transaction.Rollback;
      ShowMessage(Format('打开笔记发生错误:%s', [E.Message]));
    end;
  end;

  sql_query_note.Close;
  sql_query_note.Free;
end;

procedure Tform_main.OpenTextEditor(str_title, str_content: string;
  parent_control: TWinControl);
var
  edit_title: TEdit;
  edit_text: TMemo;
  font_info: TFontInfo;
begin
  edit_title := TEdit.Create(parent_control);
  edit_title.Name := 'edit_title';
  edit_title.Parent := parent_control;
  edit_title.Align := alTop;
  edit_title.Text := str_title;

  edit_text := TMemo.Create(parent_control);
  edit_text.Name := 'edit_text';
  edit_text.Parent := parent_control;
  edit_text.Align := alClient;
  edit_text.ScrollBars := ssBoth;
  edit_text.TabStop := False;
  edit_text.Lines.Text := str_content;

  font_info := self.ReadNoteFontConfig(ntText);
  edit_text.Font.Name := font_info.FontName;
  edit_text.Font.Size := font_info.FontSize;

  spk_tab_rich.Visible := False;
  spk_tab_markdown.Visible := False;
  spk_tab_code.Visible := False;
  spk_toolbar.TabIndex := 0;
end;

procedure Tform_main.OpenMarkdownEditor(str_title, str_content: string;
  parent_control: TWinControl);
var
  edit_title: TEdit;
  edit_text: TMemo;

  splitter: TSplitter;
  html_viewer: THtmlViewer;
  font_info: TFontInfo;
begin
  edit_title := TEdit.Create(parent_control);
  edit_title.Name := 'edit_title';
  edit_title.Parent := parent_control;
  edit_title.Align := alTop;
  edit_title.Text := str_title;

  edit_text := TMemo.Create(parent_control);
  edit_text.Name := 'edit_text';
  edit_text.Parent := parent_control;
  edit_text.Align := alClient;
  edit_text.ScrollBars := ssBoth;
  edit_text.TabStop := False;
  edit_text.Lines.Text := str_content;
  edit_text.OnChange := @OnMarkdownEditorChange;

  splitter := TSplitter.Create(parent_control);
  splitter.Name := 'markdown_splitter';
  splitter.Parent := parent_control;
  splitter.Width := 2;
  splitter.Align := alRight;

  html_viewer := THtmlViewer.Create(parent_control);
  html_viewer.Name := 'html_viewer';
  html_viewer.Parent := parent_control;
  html_viewer.Align := alRight;
  html_viewer.Width := 350;
  html_viewer.DefBackground := clWhite;

  font_info := self.ReadNoteFontConfig(ntMarkdown);
  edit_text.Font.Name := font_info.FontName;
  edit_text.Font.Size := font_info.FontSize;

  self.RenderMarkdown(edit_text);

  spk_tab_rich.Visible := False;
  spk_tab_markdown.Visible := True;
  spk_tab_code.Visible := False;
  spk_toolbar.TabIndex := 2;
end;

procedure Tform_main.OpenCodeEditor(str_title, str_content: string;
  parent_control: TWinControl);
var
  frm_code_note: Tfrm_code_note;
  font_info: TFontInfo;
begin
  frm_code_note := Tfrm_code_note.Create(parent_control);
  frm_code_note.Name := 'frm_code_note';
  frm_code_note.Parent := parent_control;
  frm_code_note.Align := alClient;
  frm_code_note.SetTitle(str_title);
  frm_code_note.SetContent(str_content);

  font_info := self.ReadNoteFontConfig(ntCode);
  frm_code_note.SetDefaultFont(font_info.FontName, font_info.FontSize);

  spk_tab_rich.Visible := False;
  spk_tab_markdown.Visible := False;
  spk_tab_code.Visible := True;
  spk_toolbar.TabIndex := 3;
end;

procedure Tform_main.OpenRichEditor(str_title, str_content: string;
  parent_control: TWinControl);
var
  edit_title: TEdit;
  edit_text: TRichMemo;
  font_info: TFontInfo;
begin
  edit_title := TEdit.Create(parent_control);
  edit_title.Name := 'edit_title';
  edit_title.Parent := parent_control;
  edit_title.Align := alTop;
  edit_title.Text := str_title;

  edit_text := TRichMemo.Create(parent_control);
  edit_text.Name := 'edit_text';
  edit_text.Parent := parent_control;
  edit_text.Align := alClient;
  edit_text.ScrollBars := ssBoth;
  edit_text.TabStop := False;
  //edit_text.PopupMenu := menu_rich;
  edit_text.OnSelectionChange := @OnRichMemoSelectionChange;
  edit_text.OnMouseUp := @OnRichMemoMouseUp;
  edit_text.OnContextPopup := @OnRichMemoContextPopup;

  font_info := self.ReadNoteFontConfig(ntRich);
  edit_text.Font.Name := font_info.FontName;
  edit_text.Font.Size := font_info.FontSize;

  edit_text.Rtf := str_content;

  spk_tab_rich.Visible := True;
  spk_tab_markdown.Visible := False;
  spk_tab_code.Visible := False;
  spk_toolbar.TabIndex := 1;
end;

procedure Tform_main.CreateRichNote;
var
  new_tabbar: TJvTabBarItem;
  new_page: TJvCustomPage;

  edit_title: TEdit;
  edit_text: TRichMemo;

  notebook_id: integer;
  note_list_text: string;
  note_data: TObject;

  selected_node: TTreeNode;
  font_info: TFontInfo;
begin
  selected_node := tv_notebook.Selected;
  if selected_node = nil then
  begin
    ShowMessage('请先选择要保存的笔记本。');
    exit;
  end;
  notebook_id := integer(selected_node.Data);

  new_tabbar := self.nav_tab_bar.AddTab('新建富文本笔记');
  new_tabbar.ImageIndex := 4;
  new_page := self.nav_page_list.Pages[new_tabbar.Index];
  new_tabbar.Selected := True;

  edit_title := TEdit.Create(new_page);
  edit_title.Name := 'edit_title';
  edit_title.Text := '';
  edit_title.TextHint := '新建富文本笔记';
  edit_title.Parent := new_page;
  edit_title.Align := alTop;

  //self.BuildRichFormatToolbar(new_page);

  edit_text := TRichMemo.Create(new_page);
  edit_text.Name := 'edit_text';
  edit_text.Lines.Text := '';
  edit_text.Parent := new_page;
  edit_text.Align := alClient;
  edit_text.ScrollBars := ssBoth;
  edit_text.TabStop := False;
  //edit_text.PopupMenu := menu_rich;
  edit_text.OnSelectionChange := @OnRichMemoSelectionChange;
  edit_text.OnMouseUp := @OnRichMemoMouseUp;
  edit_text.OnContextPopup := @OnRichMemoContextPopup;


  font_info := self.ReadNoteFontConfig(ntRich);
  edit_text.Font.Name := font_info.FontName;
  edit_text.Font.Size := font_info.FontSize;

  self.nav_page_list.ActivePageIndex := new_page.PageIndex;
  note_list_text := Format('新建富文本笔记$%s$%s',
    [FormatDateTime('yyyy-mm-dd', Now), '']);
  lb_notes.Items.Insert(0, note_list_text);

  note_data := TStringList.Create;
  TStringList(note_data).Add('0');
  TStringList(note_data).Add(IntToStr(notebook_id));
  TStringList(note_data).Add('3');
  new_tabbar.Data := note_data;

  spk_tab_rich.Visible := True;
  spk_tab_markdown.Visible := False;
  spk_tab_code.Visible := False;
  spk_toolbar.TabIndex := 1;
end;

procedure Tform_main.CreateMarkdownNote;
var
  new_tabbar: TJvTabBarItem;
  new_page: TJvCustomPage;

  edit_title: TEdit;
  edit_text: TMemo;
  //html_viewer :TJvMarkupViewer;
  splitter: TSplitter;
  html_viewer: THtmlViewer;

  notebook_id: integer;
  note_list_text: string;
  note_data: TObject;

  selected_node: TTreeNode;
  font_info: TFontInfo;
begin
  selected_node := tv_notebook.Selected;
  if selected_node = nil then
  begin
    ShowMessage('请先选择要保存的笔记本。');
    exit;
  end;
  notebook_id := integer(selected_node.Data);

  new_tabbar := self.nav_tab_bar.AddTab('新建Markdown笔记');
  new_tabbar.ImageIndex := 2;
  new_page := self.nav_page_list.Pages[new_tabbar.Index];
  new_tabbar.Selected := True;

  edit_title := TEdit.Create(new_page);
  edit_title.Name := 'edit_title';
  edit_title.Text := '';
  edit_title.TextHint := '新建Markdown笔记';
  edit_title.Parent := new_page;
  edit_title.Align := alTop;

  edit_text := TMemo.Create(new_page);
  edit_text.Name := 'edit_text';
  edit_text.Lines.Text := '';
  edit_text.Parent := new_page;
  edit_text.Align := alClient;
  edit_text.ScrollBars := ssBoth;
  edit_text.TabStop := False;
  edit_text.OnChange := @OnMarkdownEditorChange;

  splitter := TSplitter.Create(new_page);
  splitter.Name := 'markdown_splitter';
  splitter.Parent := new_page;
  splitter.Width := 2;
  splitter.Align := alRight;

  html_viewer := THtmlViewer.Create(new_page);
  html_viewer.Name := 'html_viewer';
  html_viewer.Parent := new_page;
  html_viewer.Align := alRight;
  html_viewer.Width := 350;
  html_viewer.DefBackground := clWhite;

  font_info := self.ReadNoteFontConfig(ntMarkdown);
  edit_text.Font.Name := font_info.FontName;
  edit_text.Font.Size := font_info.FontSize;

  self.nav_page_list.ActivePageIndex := new_page.PageIndex;
  note_list_text := Format('新建Markdown笔记$%s$%s',
    [FormatDateTime('yyyy-mm-dd', Now), '']);
  lb_notes.Items.Insert(0, note_list_text);

  note_data := TStringList.Create;
  TStringList(note_data).Add('0');
  TStringList(note_data).Add(IntToStr(notebook_id));
  TStringList(note_data).Add('1');
  new_tabbar.Data := note_data;

  spk_tab_rich.Visible := False;
  spk_tab_Markdown.Visible := True;
  spk_tab_code.Visible := False;
  spk_toolbar.TabIndex := 2;
end;

procedure Tform_main.RenderMarkdown(edit_text: TMemo);
var
  md: TMarkdownProcessor;
  html_viewer: TComponent;

  md_str: string;
  html: string;
begin
  html_viewer := nav_page_list.Pages[self.nav_tab_bar.SelectedTab.Index].findcomponent(
    'html_viewer') as THtmlViewer;
  if html_viewer = nil then
    exit;

  md := TMarkdownProcessor.createDialect(mdDaringFireball);
  md.AllowUnsafe := True;
  md_str := edit_text.Lines.Text;
  try
    html := md.process(md_str);
    THtmlViewer(html_viewer).LoadFromString(unicodestring(html));
  finally
    md.Free;
  end;
end;

procedure Tform_main.OnMarkdownEditorChange(Sender: TObject);
begin
  self.RenderMarkdown(TMemo(Sender));
end;

procedure Tform_main.mn_new_rich_noteClick(Sender: TObject);
begin
  self.CreateRichNote;
end;

procedure Tform_main.ImportFile(filename: string);
var
  str_file_ext: string;
  str_file_name: string;

  edit_title: TEdit;
  edit_text: TComponent;
begin
  str_file_ext := ExtractFileExt(filename);
  case str_file_ext of
    '.txt':
    begin
      self.CreateTextNote;
      edit_title := self.GetActiveTitleEdit();
      edit_text := self.GetActiveEditor();
      str_file_name := ExtractFilename(filename);
      str_file_name := ChangeFileExt(str_file_name, '');
      edit_title.Text := str_file_name;
      TMemo(edit_text).Lines.LoadFromFile(filename);
    end;
    '.md':
    begin
      self.CreateMarkdownNote;
      edit_title := self.GetActiveTitleEdit();
      edit_text := self.GetActiveEditor();
      str_file_name := ExtractFilename(filename);
      str_file_name := ChangeFileExt(str_file_name, '');
      edit_title.Text := str_file_name;
      TMemo(edit_text).Lines.LoadFromFile(filename);
    end;
    else
    begin
      ShowMessage(Format('(%s)该格式暂不支持导入，敬请期待。',
        [str_file_ext]));
    end;
  end;
end;

procedure Tform_main.CreateTextNote;
var
  edit_title: TEdit;
  edit_text: TMemo;

  new_tabbar: TJvTabBarItem;
  new_page: TJvCustomPage;

  notebook_id: integer;
  note_list_text: string;
  note_data: TObject;

  selected_node: TTreeNode;
  font_info: TFontInfo;
begin
  selected_node := tv_notebook.Selected;
  if selected_node = nil then
  begin
    ShowMessage('请先选择要保存的笔记本。');
    exit;
  end;
  notebook_id := integer(selected_node.Data);

  new_tabbar := self.nav_tab_bar.AddTab('新建文本笔记');
  new_tabbar.ImageIndex := 1;
  new_page := self.nav_page_list.Pages[new_tabbar.Index];
  new_tabbar.Selected := True;

  edit_title := TEdit.Create(new_page);
  edit_title.Name := 'edit_title';
  edit_title.Text := '';
  edit_title.TextHint := '新建文本笔记';
  edit_title.Parent := new_page;
  edit_title.Align := alTop;

  edit_text := TMemo.Create(new_page);
  edit_text.Name := 'edit_text';
  edit_text.Lines.Text := '';
  edit_text.Parent := new_page;
  edit_text.Align := alClient;
  edit_text.ScrollBars := ssBoth;
  edit_text.TabStop := False;

  font_info := self.ReadNoteFontConfig(ntText);
  edit_text.Font.Name := font_info.FontName;
  edit_text.Font.Size := font_info.FontSize;

  self.nav_page_list.ActivePageIndex := new_page.PageIndex;
  note_list_text := Format('新建文本笔记$%s$%s',
    [FormatDateTime('yyyy-mm-dd', Now), '']);
  lb_notes.Items.Insert(0, note_list_text);

  note_data := TStringList.Create;
  TStringList(note_data).Add('0');
  TStringList(note_data).Add(IntToStr(notebook_id));
  TStringList(note_data).Add(IntToStr(integer(ntText)));
  new_tabbar.Data := note_data;

  spk_tab_rich.Visible := False;
  spk_tab_Markdown.Visible := False;
  spk_tab_code.Visible := False;
  spk_toolbar.TabIndex := 0;
end;

procedure Tform_main.mn_new_text_noteClick(Sender: TObject);
begin
  self.CreateTextNote;
end;

procedure Tform_main.mn_notebook_deleteClick(Sender: TObject);
begin
  self.DeleteNotebook;
end;

procedure Tform_main.DeleteNotebook;
var
  selected_node: TTreeNode;
  node_id: integer;
  sql_query: TSQLQuery;
begin
  selected_node := tv_notebook.Selected;
  if selected_node = nil then
    exit;

  if MessageDlg('删除笔记本',
    Format('您确认要删除名称为(%s)的笔记本吗?注意，确认后该笔记本以及子笔记本以及对应的笔记会全部删除', [selected_node.Text]), mtConfirmation, [mbYes, mbNo], 0) = mrYes then
  begin
    node_id := integer(selected_node.Data);
    sql_query := TSQLQuery.Create(nil);
    try
      sql_query.SQLConnection := sqlite3_connection;
      sql_query.SQL.Text := Format('delete from notebook where id = %d;', [node_id]);
      sql_query.ExecSQL;
      sql_transaction.Commit;
    finally
      sql_query.Close;
      sql_query.Free;
    end;
    self.RefreshNotebook;
  end;
end;

procedure Tform_main.DeleteNote(note_id: integer);
var
  sql_query: TSQLQuery;
begin
  sql_query := TSQLQuery.Create(nil);
  try
    sql_query.SQLConnection := sqlite3_connection;
    sql_query.SQL.Text := Format('delete from note where id = %d;', [note_id]);
    sql_query.ExecSQL;
    sql_transaction.Commit;
  finally
    sql_query.Close;
    sql_query.Free;
  end;
end;

procedure Tform_main.mn_notebook_newClick(Sender: TObject);
begin
  self.CreateNotebook;
end;

procedure Tform_main.mn_notebook_propertyClick(Sender: TObject);
var
  form_property: Tform_notebook_property;
  selected_node: TTreeNode;
begin
  selected_node := tv_notebook.Selected;
  form_property := Tform_notebook_property.Create(self);
  form_property.edit_name.Text := selected_node.Text;
  form_property.DestNotebookId := integer(selected_node.Data);
  if form_property.ShowModal = mrOk then
  begin
    self.RefreshNotebook;
  end;
end;

procedure Tform_main.CreateNotebook;
var
  selected_node: TTreeNode;
  node_id: integer;
  notebook_name: string;
  sql: string;
  sql_query_insert: TSQLQuery;
begin
  selected_node := tv_notebook.Selected;
  notebook_name := InputBox('新建笔记本', '请输入笔记本名称：', '');
  if notebook_name = '' then
    exit;
  try
    sql_query_insert := TSQLQuery.Create(nil);
    sql_query_insert.SQLConnection := sqlite3_connection;

    if selected_node <> nil then
    begin
      node_id := integer(selected_node.Data);
      sql := 'insert into notebook (name, parent) values(:value1, :value2);';
      sql_query_insert.SQL.Text := sql;
      sql_query_insert.ParamByName('value1').AsString := notebook_name;
      sql_query_insert.ParamByName('value2').AsInteger := node_id;
    end
    else
    begin
      sql := Format('insert into notebook (name, parent) values ("%s", null);',
        [notebook_name]);
      sql_query_insert.SQL.Text := sql;
    end;
    sql_query_insert.ExecSQL;
    sql_transaction.Commit;
  except
    on E: Exception do
    begin
      sql_transaction.Rollback;
      ShowMessage(Format('新建笔记本发生错误:%s', [E.Message]));
    end;
  end;

  sql_query_insert.Close;
  sql_query_insert.Free;

  self.RefreshNotebook;

end;


procedure Tform_main.SaveNote(note_id, notebook_id: integer;
  note_type: TNoteType; note_title, note_content: string);
begin
  if note_id = 0 then
    self.AddNote(notebook_id, note_type, note_title, note_content)
  else
    self.UpdateNote(note_id, note_title, note_content);
end;

procedure Tform_main.AddNote(notebook_id: integer; note_type: TNoteType;
  note_title, note_content: string);
var
  sql: string;
  sql_query_insert: TSQLQuery;
begin
  try
    sql_query_insert := TSQLQuery.Create(nil);
    sql_query_insert.SQLConnection := sqlite3_connection;

    sql := 'insert into note (notebook, note_type, title, content, ' +
      ' create_time, update_time) values (:notebook_id, :note_type, ' +
      ' :title, :content, :create_time, :update_time);';
    sql_query_insert.SQL.Text := sql;
    sql_query_insert.ParamByName('notebook_id').AsInteger := notebook_id;
    sql_query_insert.ParamByName('note_type').AsInteger := integer(note_type);
    sql_query_insert.ParamByName('title').AsString := note_title;
    sql_query_insert.ParamByName('content').AsString := note_content;
    sql_query_insert.ParamByName('create_time').AsString :=
      FormatDateTime('yyyy-mm-dd hh:nn:ss', Now);
    sql_query_insert.ParamByName('update_time').AsString :=
      FormatDateTime('yyyy-mm-dd hh:nn:ss', Now);

    sql_query_insert.ExecSQL;
    sql_transaction.Commit;
  except
    on E: Exception do
    begin
      sql_transaction.Rollback;
      ShowMessage(Format('创建笔记发生错误:%s', [E.Message]));
    end;
  end;

  sql_query_insert.Close;
  sql_query_insert.Free;

  self.RefreshNoteList;
end;

procedure Tform_main.CopyNote(note_id, new_notebook_id: integer);
var
  sql: string;
  sql_query_insert: TSQLQuery;
begin
  try
    sql_query_insert := TSQLQuery.Create(nil);
    sql_query_insert.SQLConnection := sqlite3_connection;

    sql := 'insert into note (notebook, note_type, title, content, ' +
      ' create_time, update_time) select :notebook_id, s.note_type, ' +
      ' s.title, s.content, :create_time, :update_time from note s where ' +
      ' s.id = :note_id;';
    sql_query_insert.SQL.Text := sql;
    sql_query_insert.ParamByName('notebook_id').AsInteger := new_notebook_id;
    //sql_query_insert.ParamByName('note_type').AsInteger := integer(note_type);
    //sql_query_insert.ParamByName('title').AsString := note_title;
    //sql_query_insert.ParamByName('content').AsString := note_content;
    sql_query_insert.ParamByName('create_time').AsString :=
      FormatDateTime('yyyy-mm-dd hh:nn:ss', Now);
    sql_query_insert.ParamByName('update_time').AsString :=
      FormatDateTime('yyyy-mm-dd hh:nn:ss', Now);
    sql_query_insert.ParamByName('note_id').AsInteger := note_id;

    sql_query_insert.ExecSQL;
    sql_transaction.Commit;
  except
    on E: Exception do
    begin
      sql_transaction.Rollback;
      ShowMessage(Format('创建笔记发生错误:%s', [E.Message]));
    end;
  end;

  sql_query_insert.Close;
  sql_query_insert.Free;

  self.RefreshNoteList;
end;

procedure Tform_main.UpdateNote(note_id: integer; note_title, note_content: string);
var
  sql: string;
  sql_query_update: TSQLQuery;
begin
  try
    sql_query_update := TSQLQuery.Create(nil);
    sql_query_update.SQLConnection := sqlite3_connection;

    sql := 'update note set title = :title, content = ' +
      ' :content, update_time = :update_time where id = :note_id;';
    sql_query_update.SQL.Text := sql;
    sql_query_update.ParamByName('title').AsString := note_title;
    sql_query_update.ParamByName('content').AsString := note_content;
    sql_query_update.ParamByName('update_time').AsString :=
      FormatDateTime('yyyy-mm-dd hh:nn:ss', Now);
    sql_query_update.ParamByName('note_id').AsInteger := note_id;

    sql_query_update.ExecSQL;
    sql_transaction.Commit;
  except
    on E: Exception do
    begin
      sql_transaction.Rollback;
      ShowMessage(Format('修改笔记发生错误:%s', [E.Message]));
    end;
  end;

  sql_query_update.Close;
  sql_query_update.Free;

  self.RefreshNoteList;
end;

procedure Tform_main.mn_notebook_refreshClick(Sender: TObject);
begin
  self.RefreshNotebook;
end;

procedure Tform_main.mn_notebook_renameClick(Sender: TObject);
begin
  self.RenameNotebook;
end;

procedure Tform_main.mn_notebook_set_defaultClick(Sender: TObject);
var
  selected_node: TTreeNode;
  notebook_id: integer;
  ini: TInifile;
begin
  selected_node := tv_notebook.Selected;
  if selected_node = nil then
    exit;
  notebook_id := integer(selected_node.Data);
  if notebook_id <= 0 then
    exit;
  ini := TInifile.Create(ExtractFilePath(ParamStr(0)) + 'config.ini');
  try
    ini.WriteString('notebook', 'default_notebook', IntToStr(notebook_id));
  finally
    ini.Free;
  end;
end;

procedure Tform_main.ActionCopyNote;
var
  form_notebook: Tform_notebook;
  item_data: TStringList;
  note_id: integer;
  new_notebook_id: integer;
  spec_tab_item: TJvTabBarItem;

  item_index: integer;
begin
  item_index := self.GetItemIndexByCursorForListBox(lb_notes);
  if item_index < 0 then
    exit;

  item_data := TStringList(lb_notes.Items.Objects[item_index]);
  if item_data = nil then
    exit;
  if item_data.Count <= 0 then
    exit;

  note_id := StrToInt(item_data[0]);
  form_notebook := Tform_notebook.Create(self);
  if form_notebook.ShowModal = mrOk then
  begin
    // 若对应tab打开的话，先关闭。
    spec_tab_item := self.FindTabByNoteId(note_id);
    if spec_tab_item <> nil then
    begin
      self.nav_tab_bar.CloseTab(spec_tab_item);
    end;
    // 执行复制操作
    new_notebook_id := form_notebook.dest_notebook_id;
    self.CopyNote(note_id, new_notebook_id);
    self.RefreshNoteList;
  end;
end;

procedure Tform_main.mn_note_copyClick(Sender: TObject);
begin
  self.ActionCopyNote;
end;

procedure Tform_main.mn_note_deleteClick(Sender: TObject);
begin
  self.ActionDeleteNote;
end;

procedure Tform_main.ActionMoveNote;
var
  form_notebook: Tform_notebook;
  item_data: TStringList;
  note_id: integer;
  new_notebook_id: integer;
  spec_tab_item: TJvTabBarItem;

  item_index: integer;
begin
  item_index := self.GetItemIndexByCursorForListBox(lb_notes);
  if item_index < 0 then
    exit;

  item_data := TStringList(lb_notes.Items.Objects[item_index]);
  if item_data = nil then
    exit;
  if item_data.Count <= 0 then
    exit;

  note_id := StrToInt(item_data[0]);
  form_notebook := Tform_notebook.Create(self);
  if form_notebook.ShowModal = mrOk then
  begin
    // 若对应tab打开的话，先关闭。
    spec_tab_item := self.FindTabByNoteId(note_id);
    if spec_tab_item <> nil then
    begin
      self.nav_tab_bar.CloseTab(spec_tab_item);
    end;
    // 执行移动操作
    new_notebook_id := form_notebook.dest_notebook_id;
    self.MoveNote(note_id, new_notebook_id);
    self.RefreshNoteList;
  end;
end;

procedure Tform_main.mn_note_moveClick(Sender: TObject);
begin
  self.ActionMoveNote;
end;

procedure Tform_main.mn_note_openClick(Sender: TObject);
begin
  self.ActionOpenNote;
end;

procedure Tform_main.mn_note_renameClick(Sender: TObject);
begin
  self.ActionRenameNote;
end;

procedure Tform_main.mn_rich_copyClick(Sender: TObject);
var
  edit_text: TRichMemo;
begin
  edit_text := self.GetActiveEditor() as TRichMemo;
  if edit_text = nil then
    exit;
  if edit_text.SelLength > 0 then
    edit_text.CopyToClipboard;
end;

procedure Tform_main.mn_rich_cutClick(Sender: TObject);
var
  edit_text: TRichMemo;
begin
  edit_text := self.GetActiveEditor() as TRichMemo;
  if edit_text = nil then
    exit;
  if edit_text.SelLength > 0 then
    edit_text.CutToClipboard;
end;

procedure Tform_main.mn_rich_deleteClick(Sender: TObject);
var
  edit_text: TRichMemo;
begin
  edit_text := self.GetActiveEditor() as TRichMemo;
  if edit_text = nil then
    exit;
  if edit_text.SelLength > 0 then
    edit_text.SelText := '';
end;

procedure Tform_main.mn_rich_pasteClick(Sender: TObject);
var
  edit_text: TRichMemo;
begin
  edit_text := self.GetActiveEditor() as TRichMemo;
  if edit_text = nil then
    exit;
  if edit_text.CanPaste then
    edit_text.PasteFromClipboard;
end;

procedure Tform_main.mn_rich_redoClick(Sender: TObject);
var
  edit_text: TRichMemo;
begin
  edit_text := self.GetActiveEditor() as TRichMemo;
  if edit_text = nil then
    exit;
  if edit_text.CanRedo then
    edit_text.Redo;
end;

procedure Tform_main.mn_rich_selectallClick(Sender: TObject);
var
  edit_text: TRichMemo;
begin
  edit_text := self.GetActiveEditor() as TRichMemo;
  if edit_text = nil then
    exit;
  edit_text.SelectAll;
end;

procedure Tform_main.mn_rich_undoClick(Sender: TObject);
var
  edit_text: TRichMemo;
begin
  edit_text := self.GetActiveEditor() as TRichMemo;
  if edit_text = nil then
    exit;
  if edit_text.CanUndo then
    edit_text.Undo;
end;

procedure Tform_main.mn_tab_close_allClick(Sender: TObject);
var
  i: integer;
begin
  for i := self.nav_tab_bar.Tabs.Count - 1 downto 0 do
  begin
    self.nav_tab_bar.CloseTab(self.nav_tab_bar.Tabs[i]);
  end;
end;

procedure Tform_main.mn_tab_close_currentClick(Sender: TObject);
begin
  self.nav_tab_bar.CloseTab(self.nav_tab_bar.SelectedTab);
end;

procedure Tform_main.mn_tab_close_otherClick(Sender: TObject);
var
  i: integer;
begin
  for i := self.nav_tab_bar.Tabs.Count - 1 downto 0 do
  begin
    if self.nav_tab_bar.Tabs[i] <> self.nav_tab_bar.SelectedTab then
      self.nav_tab_bar.CloseTab(self.nav_tab_bar.Tabs[i]);
  end;
end;

procedure Tform_main.mn_visit_boolnoteClick(Sender: TObject);
begin
  OpenURL('http://www.zjsbt.cn/#/service/derivatives');
end;

procedure Tform_main.mn_visit_websiteClick(Sender: TObject);
begin
  OpenURL('http://www.zjsbt.cn');
end;

procedure Tform_main.nav_page_listMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: integer);
begin

end;

procedure Tform_main.nav_tab_barTabCloseQuery(Sender: TObject;
  Item: TJvTabBarItem; var CanClose: boolean);
var
  edit_title: TEdit;
  edit_text: TComponent;
  str_title, str_content: string;
  frm_code_note: Tfrm_code_note;

  nav_data: TObject;
  note_id, notebook_id: integer;
  note_type: TNoteType;
begin
  nav_data := self.nav_tab_bar.SelectedTab.Data;
  try

    if TStringList(nav_data).Count = 0 then
    begin
      ShowMessage('未获取到笔记信息。');
      exit;
    end;
    note_id := StrToInt(TStringList(nav_data)[0]);
    notebook_id := StrToInt(TStringList(nav_data)[1]);
    note_type := TNoteType(StrToInt(TStringList(nav_data)[2]));

    case note_type of
      ntText:
      begin
        edit_title := nav_page_list.Pages[Item.Index].findcomponent(
          'edit_title') as TEdit;
        str_title := edit_title.Text;
        if str_title = '' then
          str_title := '新建文本笔记';
        edit_text := nav_page_list.Pages[Item.Index].findcomponent('edit_text');
        str_content := TMemo(edit_text).Lines.Text;
      end;
      ntMarkdown:
      begin
        edit_title := nav_page_list.Pages[Item.Index].findcomponent(
          'edit_title') as TEdit;
        str_title := edit_title.Text;
        if str_title = '' then
          str_title := '新建Markdown笔记';
        edit_text := nav_page_list.Pages[Item.Index].findcomponent('edit_text');
        str_content := TMemo(edit_text).Lines.Text;
        spk_tab_markdown.Visible := False;
        spk_toolbar.TabIndex := 0;
      end;
      ntCode:
      begin
        //edit_title := nav_page_list.Pages[Item.Index].findcomponent('edit_title') as TEdit;
        //str_title := edit_title.Text;
        //edit_text := nav_page_list.Pages[Item.Index].findcomponent('edit_text');
        //str_content := TSynEdit(edit_text).Lines.Text;
        frm_code_note := nav_page_list.Pages[Item.Index].findcomponent(
          'frm_code_note') as Tfrm_code_note;
        str_title := frm_code_note.GetTitle();
        if str_title = '' then
          str_title := '新建代码笔记';
        str_content := frm_code_note.GetContent();
        spk_tab_code.Visible := False;
        spk_toolbar.TabIndex := 0;
      end;
      ntRich:
      begin
        edit_title := nav_page_list.Pages[Item.Index].findcomponent(
          'edit_title') as TEdit;
        str_title := edit_title.Text;
        if str_title = '' then
          str_title := '新建富文本笔记';
        edit_text := nav_page_list.Pages[Item.Index].findcomponent('edit_text') as
          TRichMemo;
        //str_content := TRichMemo(edit_text).Lines.Text;
        str_content := TRichMemo(edit_text).Rtf;
        spk_tab_rich.Visible := False;
        spk_toolbar.TabIndex := 0;
      end;
      else
      begin
        edit_title := nav_page_list.Pages[Item.Index].findcomponent(
          'edit_title') as TEdit;
        str_title := edit_title.Text;
        edit_text := nav_page_list.Pages[Item.Index].findcomponent('edit_text');
        str_content := TMemo(edit_text).Lines.Text;
      end;
    end;
    self.SaveNote(note_id, notebook_id, note_type, str_title, str_content);
  finally
    nav_data.Free;
  end;
end;

procedure Tform_main.RenameNote(note_id: integer; new_note_title: string);
var
  sql_query: TSQLQuery;
  sql: string;
begin
  sql_query := TSQLQuery.Create(nil);
  try
    sql_query.SQLConnection := sqlite3_connection;
    sql := Format('update note set title = "%s" where id = %d;',
      [new_note_title, note_id]);
    sql_query.SQL.Text := sql;
    sql_query.ExecSQL;
    sql_transaction.Commit;
  finally
    sql_query.Close;
    sql_query.Free;
  end;
end;

procedure Tform_main.MoveNote(note_id, new_notebook_id: integer);
var
  sql_query: TSQLQuery;
  sql: string;
begin
  sql_query := TSQLQuery.Create(nil);
  try
    sql_query.SQLConnection := sqlite3_connection;
    sql := Format('update note set notebook = "%d" where id = %d;',
      [new_notebook_id, note_id]);
    sql_query.SQL.Text := sql;
    sql_query.ExecSQL;
    sql_transaction.Commit;
  finally
    sql_query.Close;
    sql_query.Free;
  end;
end;

procedure Tform_main.RenameNotebook;
var
  selected_node: TTreeNode;
  node_id: integer;
  sql_query: TSQLQuery;
  sql: string;
  new_name: string;
begin
  selected_node := tv_notebook.Selected;
  if selected_node = nil then
    exit;
  node_id := integer(selected_node.Data);
  new_name := InputBox('笔记本重命名：', '请输入新的笔记本名称：',
    selected_node.Text);
  if new_name = '' then
    exit;
  sql_query := TSQLQuery.Create(nil);
  try
    sql_query.SQLConnection := sqlite3_connection;
    sql := Format('update notebook set name = "%s" where id = %d;',
      [new_name, node_id]);
    sql_query.SQL.Text := sql;
    sql_query.ExecSQL;
    sql_transaction.Commit;
  finally
    sql_query.Close;
    sql_query.Free;
  end;
  self.RefreshNotebook;
end;

procedure Tform_main.nav_tab_barTabSelected(Sender: TObject; Item: TJvTabBarItem);
var
  nav_data: TObject;
  note_type: integer;
begin
  if Item = nil then
    exit;

  nav_data := TStringList(Item.Data);
  if nav_data = nil then
    exit;
  note_type := StrToInt(TStringList(nav_data)[2]);
  case note_type of
    integer(ntRich):
    begin
      spk_tab_rich.Visible := True;
      spk_tab_markdown.Visible := False;
      spk_tab_code.Visible := False;
      spk_toolbar.TabIndex := 1;
    end;
    integer(ntMarkdown):
    begin
      spk_tab_rich.Visible := False;
      spk_tab_markdown.Visible := True;
      spk_tab_code.Visible := False;
      spk_toolbar.TabIndex := 2;
    end;
    integer(ntCode):
    begin
      spk_tab_rich.Visible := False;
      spk_tab_markdown.Visible := False;
      spk_tab_code.Visible := True;
      spk_toolbar.TabIndex := 3;
    end
    else
    begin
      spk_tab_rich.Visible := False;
      spk_tab_markdown.Visible := False;
      spk_tab_code.Visible := False;
    end;
  end;
end;

procedure Tform_main.replace_dialogReplace(Sender: TObject);
var
  edit_text: TComponent;

  opt: TFindOptions;
  syn_search_opt: TSynSearchOptions;
  search_opt: TSearchOptions;

  nav_data: TObject;
  note_type: TNoteType;

  i: integer;
  find_text :string;
  search_len: integer;
begin
  // 获取当前编辑器
  edit_text := self.GetActiveEditor();
  if edit_text = nil then
    exit;

  // 判断当前笔记类型
  nav_data := self.nav_tab_bar.SelectedTab.Data;
  if TStringList(nav_data).Count = 0 then
  begin
    ShowMessage('未获取到笔记信息。');
    exit;
  end;
  note_type := TNoteType(StrToInt(TStringList(nav_data)[2]));

  with Sender as TReplaceDialog do
  begin
    case note_type of
      ntText:
      begin
        FindText := TMemo(edit_text).SelText;
        opt := [];
        if frReplace in Options then
          opt := [frReplace];
        if frReplaceAll in Options then
          opt := [frReplaceAll];

        i := UTF8Pos(FindText, TMemo(edit_text).Lines.Text);
        if i > 0 then
        begin
          TMemo(edit_text).SelStart := i - 1;
          TMemo(edit_text).SelLength := UTF8Length(FindText);
          TMemo(edit_text).SelText := ReplaceText;
          TMemo(edit_text).SetFocus;
        end
        else
          Beep;
      end;
      ntMarkdown:
      begin
        FindText := TMemo(edit_text).SelText;
        opt := [];
        if frReplace in Options then
          opt := [frReplace];
        if frReplaceAll in Options then
          opt := [frReplaceAll];

        i := UTF8Pos(FindText, TMemo(edit_text).Lines.Text);
        if i > 0 then
        begin
          TMemo(edit_text).SelStart := i - 1;
          TMemo(edit_text).SelLength := UTF8Length(FindText);
          TMemo(edit_text).SelText := ReplaceText;
          TMemo(edit_text).SetFocus;
        end
        else
          Beep;
      end;
      ntCode:
      begin
        FindText := TSynEdit(edit_text).SelText;
        syn_search_opt := [];
        if frReplace in Options then
          syn_search_opt := [ssoReplace];
        if frReplaceAll in Options then
          syn_search_opt := [ssoReplaceAll];

        i := TSynEdit(edit_text).SearchReplaceEx(FindText, ReplaceText,
          syn_search_opt, Position);
        if i > 0 then
          TSynEdit(edit_text).SetFocus
        else
          Beep;
      end;
      ntRich:
      begin
        //FindText := TRichMemo(edit_text).SelText;
        //if frReplace in Options then
        //  opt := [frReplace];
        //if frReplaceAll in Options then
        //  opt := [frReplaceAll];
        search_opt := [soWholeWord];
        //if TRichMemo(edit_text).Search(FindText, 0, UTF8Length(FindText),
        //  search_opt, i, search_result_len) then
        find_text := FindText;
        search_len := UTF8Length(FindText);
        //i := TRichMemo(edit_text).Search(FindText, 0, UTF8Length(FindText),
        //  search_opt);
        i := TRichMemo(edit_text).Search(find_text, 0, search_len, search_opt);
        if i >= 0 then
        begin
          TRichMemo(edit_text).SelStart := i;
          TRichMemo(edit_text).SelText := ReplaceText;
          TRichMemo(edit_text).SetFocus;
        end
        else
          Beep;
      end;
    end;
  end;
end;

procedure Tform_main.spk_btn_code_copyClick(Sender: TObject);
var
  edit_text: TSynEdit;
begin
  edit_text := self.GetActiveEditor() as TSynEdit;
  if edit_text = nil then
    exit;
  if edit_text.SelText.Length > 0 then
    edit_text.CopyToClipboard;
end;

procedure Tform_main.spk_btn_code_cutClick(Sender: TObject);
var
  edit_text: TSynEdit;
begin
  edit_text := self.GetActiveEditor() as TSynEdit;
  if edit_text = nil then
    exit;
  if edit_text.SelText.Length > 0 then
    edit_text.CutToClipboard;
end;

procedure Tform_main.spk_btn_code_findClick(Sender: TObject);
var
  edit_text: TSynEdit;
begin
  edit_text := self.GetActiveEditor() as TSynEdit;
  if edit_text = nil then
    exit;

  find_dialog.FindText := edit_text.SelText;
  find_dialog.Execute;
end;

procedure Tform_main.spk_btn_code_pasteClick(Sender: TObject);
var
  edit_text: TSynEdit;
begin
  edit_text := self.GetActiveEditor() as TSynEdit;
  if edit_text = nil then
    exit;
  if edit_text.CanPaste then
    edit_text.PasteFromClipboard(False);
end;

procedure Tform_main.spk_btn_code_redoClick(Sender: TObject);
var
  edit_text: TSynEdit;
begin
  edit_text := self.GetActiveEditor() as TSynEdit;
  if edit_text = nil then
    exit;
  if edit_text.CanRedo then
    edit_text.Redo;
end;

procedure Tform_main.spk_btn_code_replaceClick(Sender: TObject);
begin
  self.replace_dialog.Execute;
end;

procedure Tform_main.spk_btn_code_undoClick(Sender: TObject);
var
  edit_text: TSynEdit;
begin
  edit_text := self.GetActiveEditor() as TSynEdit;
  if edit_text = nil then
    exit;
  if edit_text.CanUndo then
    edit_text.Undo;
end;

procedure Tform_main.spk_btn_file_create_codeClick(Sender: TObject);
begin
  self.CreateCodeNote;
end;

procedure Tform_main.spk_btn_file_create_markdownClick(Sender: TObject);
begin
  self.CreateMarkdownNote;
end;

procedure Tform_main.spk_btn_file_create_richClick(Sender: TObject);
begin
  self.CreateRichNote;
end;

procedure Tform_main.spk_btn_file_create_textClick(Sender: TObject);
begin
  self.CreateTextNote;
end;

procedure Tform_main.spk_btn_file_importClick(Sender: TObject);
var
  import_file_name: string;
begin
  self.import_dialog.Filter := '文本文件(*.txt)|*.txt|Markdown文件(*.md)|*.md';
  if import_dialog.Execute then
  begin
    import_file_name := import_dialog.FileName;
    self.ImportFile(import_file_name);
  end;
end;

procedure Tform_main.spk_btn_md_boldClick(Sender: TObject);
var
  edit_text: TMemo;
  pos_start, pos_end: integer;
begin
  edit_text := self.GetActiveEditor() as TMemo;
  if edit_text = nil then
    exit;

  pos_start := edit_text.SelStart;
  pos_end := edit_text.SelStart + edit_text.SelLength + 2;
  edit_text.SelStart := pos_start;
  edit_text.SelText := '**';
  edit_text.SelStart := pos_end;
  edit_text.SelText := '**';
end;

procedure Tform_main.spk_btn_md_code_blockClick(Sender: TObject);
var
  edit_text: TMemo;
  code_str: string;
begin
  edit_text := self.GetActiveEditor() as TMemo;
  if edit_text = nil then
    exit;

  code_str := '```' + sLineBreak + '```';
  self.InsertMarkdownElement(edit_text, code_str);
end;

procedure Tform_main.spk_btn_md_code_lineClick(Sender: TObject);
var
  edit_text: TMemo;
  pos_start, pos_end: integer;
begin
  edit_text := self.GetActiveEditor() as TMemo;
  if edit_text = nil then
    exit;

  pos_start := edit_text.SelStart;
  pos_end := edit_text.SelStart + edit_text.SelLength + 1;
  edit_text.SelStart := pos_start;
  edit_text.SelText := '`';
  edit_text.SelStart := pos_end;
  edit_text.SelText := '`';
end;

procedure Tform_main.spk_btn_md_copyClick(Sender: TObject);
var
  edit_text: TMemo;
begin
  edit_text := self.GetActiveEditor() as TMemo;
  if edit_text = nil then
    exit;
  if edit_text.SelLength > 0 then
    edit_text.CopyToClipboard;
end;

procedure Tform_main.spk_btn_md_cutClick(Sender: TObject);
var
  edit_text: TMemo;
begin
  edit_text := self.GetActiveEditor() as TMemo;
  if edit_text = nil then
    exit;
  if edit_text.SelLength > 0 then
    edit_text.CutToClipboard;
end;

procedure Tform_main.spk_btn_md_findClick(Sender: TObject);
var
  edit_text: TMemo;
begin
  edit_text := self.GetActiveEditor() as TMemo;
  if edit_text = nil then
    exit;

  find_dialog.FindText := edit_text.SelText;
  find_dialog.Execute;
end;

procedure Tform_main.spk_btn_md_hyperlinkClick(Sender: TObject);
var
  edit_text: TMemo;
begin
  edit_text := self.GetActiveEditor() as TMemo;
  if edit_text = nil then
    exit;

  self.InsertMarkdownElement(edit_text, '[]()');
end;

procedure Tform_main.spk_btn_md_imageClick(Sender: TObject);
var
  edit_text: TMemo;
begin
  edit_text := self.GetActiveEditor() as TMemo;
  if edit_text = nil then
    exit;

  self.InsertMarkdownElement(edit_text, '![图片alt](图片链接 "图片title")');
end;

procedure Tform_main.spk_btn_md_italicClick(Sender: TObject);
var
  edit_text: TMemo;
  pos_start, pos_end: integer;
begin
  edit_text := self.GetActiveEditor() as TMemo;
  if edit_text = nil then
    exit;

  pos_start := edit_text.SelStart;
  pos_end := edit_text.SelStart + edit_text.SelLength + 1;
  edit_text.SelStart := pos_start;
  edit_text.SelText := '*';
  edit_text.SelStart := pos_end;
  edit_text.SelText := '*';
end;

procedure Tform_main.spk_btn_md_lineClick(Sender: TObject);
var
  edit_text: TMemo;
begin
  edit_text := self.GetActiveEditor() as TMemo;
  if edit_text = nil then
    exit;

  self.InsertMarkdownElement(edit_text, '---');
end;

procedure Tform_main.spk_btn_md_orderedlistClick(Sender: TObject);
var
  edit_text: TMemo;
begin
  edit_text := self.GetActiveEditor() as TMemo;
  if edit_text = nil then
    exit;
  self.InsertMarkdownElementAhead(edit_text, '1. ');
end;

procedure Tform_main.spk_btn_md_pasteClick(Sender: TObject);
var
  edit_text: TMemo;
begin
  edit_text := self.GetActiveEditor() as TMemo;
  if edit_text = nil then
    exit;
  edit_text.PasteFromClipboard;
end;

procedure Tform_main.spk_btn_md_quoteClick(Sender: TObject);
var
  edit_text: TMemo;
begin
  edit_text := self.GetActiveEditor() as TMemo;
  if edit_text = nil then
    exit;

  self.InsertMarkdownElementAhead(edit_text, '> ');
end;

procedure Tform_main.spk_btn_md_replaceClick(Sender: TObject);
begin
  self.replace_dialog.Execute;
end;

procedure Tform_main.spk_btn_md_strikeoutClick(Sender: TObject);
var
  edit_text: TMemo;
  pos_start, pos_end: integer;
begin
  edit_text := self.GetActiveEditor() as TMemo;
  if edit_text = nil then
    exit;

  pos_start := edit_text.SelStart;
  pos_end := edit_text.SelStart + edit_text.SelLength + 2;
  edit_text.SelStart := pos_start;
  edit_text.SelText := '~~';
  edit_text.SelStart := pos_end;
  edit_text.SelText := '~~';
end;

procedure Tform_main.spk_btn_md_subscriptClick(Sender: TObject);
var
  edit_text: TMemo;
  pos_start, pos_end: integer;
begin
  edit_text := self.GetActiveEditor() as TMemo;
  if edit_text = nil then
    exit;

  pos_start := edit_text.SelStart;
  pos_end := edit_text.SelStart + edit_text.SelLength + 1;
  edit_text.SelStart := pos_start;
  edit_text.SelText := '^';
  edit_text.SelStart := pos_end;
  edit_text.SelText := '^';
end;

procedure Tform_main.spk_btn_md_superscriptClick(Sender: TObject);
var
  edit_text: TMemo;
  pos_start, pos_end: integer;
begin
  edit_text := self.GetActiveEditor() as TMemo;
  if edit_text = nil then
    exit;

  pos_start := edit_text.SelStart;
  pos_end := edit_text.SelStart + edit_text.SelLength + 1;
  edit_text.SelStart := pos_start;
  edit_text.SelText := '~';
  edit_text.SelStart := pos_end;
  edit_text.SelText := '~';
end;

procedure Tform_main.spk_btn_md_tableClick(Sender: TObject);
var
  edit_text: TMemo;
  table_str: string;
begin
  edit_text := self.GetActiveEditor() as TMemo;
  if edit_text = nil then
    exit;

  table_str := '| Syntax      | Description |' + sLineBreak +
    '| ----------- | ----------- |' + sLineBreak + '| Header      | Title       |' +
    sLineBreak + '| Paragraph   | Text        |' + sLineBreak;
  self.InsertMarkdownElement(edit_text, table_str);
end;

procedure Tform_main.spk_btn_md_todoClick(Sender: TObject);
var
  edit_text: TMemo;
begin
  edit_text := self.GetActiveEditor() as TMemo;
  if edit_text = nil then
    exit;

  self.InsertMarkdownElementAhead(edit_text, '- [ ] ');
end;

function Tform_main.GetCursorLine(Memo: TMemo): integer;
var
  I, StartPos: integer;
  sel_start: integer;
  curr_line: string;
begin
  Result := -1; // 如果没有找到，返回-1
  StartPos := 0;

  sel_start := Memo.SelStart;
  for I := 0 to Memo.Lines.Count - 1 do
  begin
    // 累加当前行的长度和行尾的换行符长度
    curr_line := Memo.Lines[I];
    //StartPos := StartPos + Length(curr_line.Length) + 1;
    StartPos := StartPos + UTF8Length(curr_line) + 1;

    // 如果累加的位置超过了光标的位置，那么光标就在这一行
    if StartPos > sel_start + 1 then
    begin
      Result := I;
      Break;
    end;
    // 增加换行符的长度（在TMemo中通常是1）
    Inc(StartPos);
  end;
end;


procedure Tform_main.InsertMarkdownElementAhead(edit_text: TMemo; str_element: string);
var
  line_num: integer;
  line_str: string;
begin
  line_num := self.GetCursorLine(edit_text);

  if line_num >= 0 then
  begin
    line_str := edit_text.Lines[line_num];
    line_str := str_element + line_str;
    edit_text.Lines[line_num] := line_str;
  end
  else
  begin
    edit_text.SelText := str_element;
  end;
end;

procedure Tform_main.spk_btn_md_unorderedlistClick(Sender: TObject);
var
  edit_text: TMemo;
begin
  edit_text := self.GetActiveEditor() as TMemo;
  if edit_text = nil then
    exit;
  self.InsertMarkdownElementAhead(edit_text, '- ');
end;

function Tform_main.GetActiveTitleEdit(): TEdit;
var
  edit_title: TEdit;
  page_index: integer;
begin
  page_index := self.nav_tab_bar.SelectedTab.Index;
  edit_title := nav_page_list.Pages[page_index].findcomponent('edit_title') as TEdit;
  Result := edit_title;
end;


function Tform_main.GetActiveEditor(): TComponent;
var
  edit_text: TComponent;
  page_index: integer;

  frm_code_note: Tfrm_code_note;
  note_type: TNoteType;
  nav_data: TStringList;
begin
  page_index := self.nav_tab_bar.SelectedTab.Index;
  if page_index < 0 then
    exit;
  nav_data := TStringList(self.nav_tab_bar.Tabs[page_index].Data);
  if nav_data = nil then
    exit;
  note_type := TNoteType(StrToInt(nav_data[2]));
  if note_type = ntCode then
  begin
    frm_code_note := nav_page_list.Pages[page_index].findcomponent('frm_code_note') as
      Tfrm_code_note;
    edit_text := frm_code_note.edit_text;
  end
  else
  begin
    edit_text := nav_page_list.Pages[page_index].findcomponent('edit_text');
  end;
  Result := edit_text;
end;

procedure Tform_main.SetRichEditorAlignment(edit_text: TRichMemo;
  alignment: TParaAlignment);
begin
  if edit_text = nil then
    exit;
  if edit_text.SelLength = 0 then
    exit;
  edit_text.SetParaAlignment(edit_text.SelStart, edit_text.SelLength, alignment);
end;

procedure Tform_main.spk_btn_rich_align_centerClick(Sender: TObject);
var
  edit_text: TRichMemo;
begin
  edit_text := self.GetActiveEditor() as TRichMemo;
  if edit_text <> nil then
  begin
    self.SetRichEditorAlignment(edit_text, paCenter);
    spk_btn_rich_align_left.Checked := False;
    spk_btn_rich_align_center.Checked := True;
    spk_btn_rich_align_right.Checked := False;
    spk_btn_rich_align_justify.Checked := False;
  end;
end;

procedure Tform_main.spk_btn_rich_align_justifyClick(Sender: TObject);
var
  edit_text: TRichMemo;
begin
  edit_text := self.GetActiveEditor() as TRichMemo;
  if edit_text <> nil then
  begin
    self.SetRichEditorAlignment(edit_text, paJustify);
    spk_btn_rich_align_left.Checked := False;
    spk_btn_rich_align_center.Checked := False;
    spk_btn_rich_align_right.Checked := False;
    spk_btn_rich_align_justify.Checked := True;
  end;
end;

procedure Tform_main.spk_btn_rich_align_leftClick(Sender: TObject);
var
  edit_text: TRichMemo;
begin
  edit_text := self.GetActiveEditor() as TRichMemo;
  if edit_text <> nil then
  begin
    self.SetRichEditorAlignment(edit_text, paLeft);
    spk_btn_rich_align_left.Checked := True;
    spk_btn_rich_align_center.Checked := False;
    spk_btn_rich_align_right.Checked := False;
    spk_btn_rich_align_justify.Checked := False;
  end;
end;

procedure Tform_main.spk_btn_rich_align_rightClick(Sender: TObject);
var
  edit_text: TRichMemo;
begin
  edit_text := self.GetActiveEditor() as TRichMemo;
  if edit_text <> nil then
  begin
    self.SetRichEditorAlignment(edit_text, paRight);
    spk_btn_rich_align_left.Checked := False;
    spk_btn_rich_align_center.Checked := False;
    spk_btn_rich_align_right.Checked := True;
    spk_btn_rich_align_justify.Checked := False;
  end;
end;

procedure Tform_main.spk_btn_rich_back_colorClick(Sender: TObject);
var
  edit_text: TRichMemo;
  font_params: TFontParams = (Name: '宋体'; Size: 9; Color: clBlack;
  Style: []; HasBkClr: True; BkColor: clWhite; VScriptPos: vpNormal);
begin
  edit_text := self.GetActiveEditor() as TRichMemo;
  if edit_text = nil then
    exit;

  if color_dialog.Execute then
  begin
    if edit_text.GetTextAttributes(edit_text.SelStart, font_params) then
    begin
      font_params.BkColor := color_dialog.Color;
      edit_text.SetTextAttributes(edit_text.SelStart, edit_text.SelLength, font_params);
      edit_text.SetRangeParams(edit_text.SelStart, edit_text.SelLength,
        [tmm_Styles, tmm_BackColor], '', 0, clBlack, [], []);
    end;
  end;
end;

procedure Tform_main.SetRichEditorFontName(edit_text: TRichMemo; font_name: string);
var
  font_params: TFontParams = (Name: '宋体'; Size: 9; Color: clBlack;
  Style: []; HasBkClr: True; BkColor: clWhite; VScriptPos: vpNormal);
begin
  if edit_text = nil then
    exit;
  if edit_text.SelLength = 0 then
    exit;

  if edit_text.GetTextAttributes(edit_text.SelStart, font_params) then
  begin
    font_params.Name := font_name;
    edit_text.SetTextAttributes(edit_text.SelStart, edit_text.SelLength, font_params);
  end;
end;

procedure Tform_main.SetRichEditorFontSize(edit_text: TRichMemo; font_size: integer);
var
  font_params: TFontParams = (Name: '宋体'; Size: 9; Color: clBlack;
  Style: []; HasBkClr: True; BkColor: clWhite; VScriptPos: vpNormal);
begin
  if edit_text = nil then
    exit;
  if edit_text.SelLength = 0 then
    exit;

  if edit_text.GetTextAttributes(edit_text.SelStart, font_params) then
  begin
    font_params.Size := font_size;
    edit_text.SetTextAttributes(edit_text.SelStart, edit_text.SelLength, font_params);
  end;
end;

procedure Tform_main.SetRichEditorFontStyle(edit_text: TRichMemo;
  related_control: TComponent; font_style: TFontStyle);
var
  font_params: TFontParams = (Name: '宋体'; Size: 9; Color: clBlack;
  Style: []; HasBkClr: True; BkColor: clWhite; VScriptPos: vpNormal);
begin
  if edit_text = nil then
    exit;
  if edit_text.SelLength = 0 then
    exit;

  if edit_text.GetTextAttributes(edit_text.SelStart, font_params) then
  begin
    if TSpkSmallButton(related_control).Checked then
      font_params.Style := font_params.Style + [font_style]
    else
      font_params.Style := font_params.Style - [font_style];
    edit_text.SetTextAttributes(edit_text.SelStart, edit_text.SelLength, font_params);
  end;
end;

procedure Tform_main.spk_btn_rich_boldClick(Sender: TObject);
var
  edit_text: TRichMemo;
begin
  edit_text := self.GetActiveEditor() as TRichMemo;
  if edit_text = nil then
    exit;
  self.SetRichEditorFontStyle(edit_text, spk_btn_rich_bold, fsBold);
end;

procedure Tform_main.spk_btn_rich_copyClick(Sender: TObject);
var
  edit_text: TRichMemo;
begin
  edit_text := self.GetActiveEditor() as TRichMemo;
  if edit_text = nil then
    exit;
  if edit_text.SelLength > 0 then
    edit_text.CopyToClipboard;
end;

procedure Tform_main.spk_btn_rich_cutClick(Sender: TObject);
var
  edit_text: TRichMemo;
begin
  edit_text := self.GetActiveEditor() as TRichMemo;
  if edit_text = nil then
    exit;
  if edit_text.SelLength > 0 then
    edit_text.CutToClipboard;
end;

procedure Tform_main.spk_btn_rich_findClick(Sender: TObject);
var
  edit_text: TRichMemo;
begin
  edit_text := self.GetActiveEditor() as TRichMemo;
  if edit_text = nil then
    exit;

  find_dialog.FindText := edit_text.SelText;
  find_dialog.Execute;
end;

procedure Tform_main.spk_btn_rich_italicClick(Sender: TObject);
var
  edit_text: TRichMemo;
begin
  edit_text := self.GetActiveEditor() as TRichMemo;
  if edit_text = nil then
    exit;
  self.SetRichEditorFontStyle(edit_text, spk_btn_rich_italic, fsItalic);
end;

procedure Tform_main.SetRichEditorParaNumbering(edit_text: TRichMemo;
  list_style: TParaNumStyle);
var
  page_numbering: TParaNumbering;
begin
  if edit_text.GetParaNumbering(edit_text.SelStart, page_numbering) then
  begin
    page_numbering.Style := list_style;
    edit_text.SetParaNumbering(edit_text.SelStart, edit_text.SelLength, page_numbering);
  end;
end;

procedure Tform_main.spk_btn_rich_numering_bulletClick(Sender: TObject);
var
  edit_text: TRichMemo;
begin
  edit_text := self.GetActiveEditor() as TRichMemo;
  if edit_text = nil then
    exit;
  self.SetRichEditorParaNumbering(edit_text, pnBullet);
end;

procedure Tform_main.spk_btn_rich_numering_noneClick(Sender: TObject);
var
  edit_text: TRichMemo;
begin
  edit_text := self.GetActiveEditor() as TRichMemo;
  if edit_text = nil then
    exit;
  self.SetRichEditorParaNumbering(edit_text, pnNone);
end;

procedure Tform_main.spk_btn_rich_numering_numberClick(Sender: TObject);
var
  edit_text: TRichMemo;
begin
  edit_text := self.GetActiveEditor() as TRichMemo;
  if edit_text = nil then
    exit;
  self.SetRichEditorParaNumbering(edit_text, pnNumber);
end;

procedure Tform_main.spk_btn_rich_pasteClick(Sender: TObject);
var
  edit_text: TRichMemo;
begin
  edit_text := self.GetActiveEditor() as TRichMemo;
  if edit_text = nil then
    exit;
  if edit_text.CanPaste then
    edit_text.PasteFromClipboard;
end;

procedure Tform_main.spk_btn_rich_replaceClick(Sender: TObject);
begin
  self.replace_dialog.Execute;
end;

procedure Tform_main.spk_btn_rich_underlineClick(Sender: TObject);
var
  edit_text: TRichMemo;
begin
  edit_text := self.GetActiveEditor() as TRichMemo;
  if edit_text = nil then
    exit;
  self.SetRichEditorFontStyle(edit_text, spk_btn_rich_underline, fsUnderline);
end;

procedure Tform_main.tv_notebookChange(Sender: TObject; Node: TTreeNode);
begin
  self.ReleaseNoteListObjects;
  self.RefreshNoteList;
end;

procedure Tform_main.spk_btn_rich_strikeoutClick(Sender: TObject);
var
  edit_text: TRichMemo;
begin
  edit_text := self.GetActiveEditor() as TRichMemo;
  if edit_text = nil then
    exit;
  self.SetRichEditorFontStyle(edit_text, spk_btn_rich_strikeout, fsStrikeOut);
end;

procedure Tform_main.spk_btn_rich_font_colorClick(Sender: TObject);
var
  edit_text: TRichMemo;
begin
  edit_text := self.GetActiveEditor() as TRichMemo;
  if edit_text = nil then
    exit;

  if color_dialog.Execute then
  begin
    edit_text.SetRangeColor(edit_text.SelStart, edit_text.SelLength,
      color_dialog.Color);
  end;
end;

procedure Tform_main.tv_notebookCustomDraw(Sender: TCustomTreeView;
  const ARect: TRect; var DefaultDraw: boolean);
//var
//  img_rect: TRect;
//  back_image: TPortableNetworkGraphic;
//  app_path: string;
begin
  //img_rect := ARect;
  //app_path := ExtractFilePath(ParamStr(0));
  //app_path := Format('%simages\blur_background.png', [app_path]);
  //try
  //  back_image := TPortableNetworkGraphic.Create;
  //  back_image.LoadFromFile(app_path);
  //  Sender.Canvas.StretchDraw(img_rect, back_image);
  //except
  //  on E: Exception do
  //  begin
  //    ShowMessage(Format(
  //      '加载笔记本背景栏失败，请确定文件是否存在？', [E.Message]));
  //  end;
  //end;
end;

procedure Tform_main.OnRichMemoSelectionChange(Sender: TObject);
var
  edit_text: TRichMemo;
  text_params: TFontParams = (Name: '宋体'; Size: 9; Color: clBlack;
  Style: []; HasBkClr: True; BkColor: clWhite; VScriptPos: vpNormal);
  alignment: TParaAlignment;
begin
  edit_text := TRichMemo(Sender);
  alignment := paLeft;

  //字体样式
  edit_text.GetTextAttributes(edit_text.SelStart, text_params);
  self.spk_btn_rich_bold.Checked := fsBold in text_params.Style;
  self.spk_btn_rich_italic.Checked := fsItalic in text_params.Style;
  self.spk_btn_rich_underline.Checked := fsUnderline in text_params.Style;
  self.spk_btn_rich_strikeout.Checked := fsStrikeOut in text_params.Style;

  //对齐方式
  if edit_text.GetParaAlignment(edit_text.SelStart, alignment) then
  begin
    self.spk_btn_rich_align_left.Checked := (paLeft = alignment);
    self.spk_btn_rich_align_center.Checked := (paCenter = alignment);
    self.spk_btn_rich_align_right.Checked := (paRight = alignment);
    self.spk_btn_rich_align_justify.Checked := (paJustify = alignment);
  end;

  // 剪贴板
  spk_btn_rich_cut.Enabled := edit_text.SelLength > 0;
  spk_btn_rich_copy.Enabled := edit_text.SelLength > 0;
  //mn_rich_cut.Enabled:= edit_text.SelLength > 0;
  //mn_rich_copy.Enabled:= edit_text.SelLength > 0;
end;

procedure Tform_main.OnRichMemoMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: integer);
var
  edit_text: TRichMemo;
begin
  edit_text := TRichMemo(Sender);
  // 剪贴板
  spk_btn_rich_cut.Enabled := edit_text.SelLength > 0;
  spk_btn_rich_copy.Enabled := edit_text.SelLength > 0;
end;

procedure Tform_main.OnRichMemoContextPopup(Sender: TObject; MousePos: TPoint;
  var Handled: boolean);
var
  client_pos: TPoint;
  edit_text: TRichMemo;
begin
  edit_text := TRichMemo(Sender);
  client_pos := edit_text.ClientToScreen(MousePos);
  mn_rich_undo.Enabled := edit_text.CanUndo;
  mn_rich_redo.Enabled := edit_text.CanRedo;
  mn_rich_paste.Enabled := edit_text.CanPaste;

  mn_rich_cut.Enabled := Length(edit_text.SelText) > 0;
  mn_rich_copy.Enabled := Length(edit_text.SelText) > 0;
  mn_rich_delete.Enabled := Length(edit_text.SelText) > 0;

  menu_rich.PopUp(client_pos.X, client_pos.Y);
end;

procedure Tform_main.OnRichEditorFontNameClick(Sender: TObject);
var
  selected_font: string;
  edit_text: TRichMemo;
begin
  selected_font := TMenuItem(Sender).Caption;
  edit_text := self.GetActiveEditor() as TRichMemo;
  if edit_text = nil then
    exit;
  self.SetRichEditorFontName(edit_text, selected_font);
end;

procedure Tform_main.OnCodeEditorSynClick(Sender: TObject);
var
  frm_code_note: Tfrm_code_note;
  syn_name: string;
  page_index: integer;
begin
  syn_name := TMenuItem(Sender).Caption;
  page_index := self.nav_tab_bar.SelectedTab.Index;
  frm_code_note := nav_page_list.Pages[page_index].findcomponent('frm_code_note') as
    Tfrm_code_note;
  if frm_code_note = nil then
    exit;
  frm_code_note.SetEditorSyn(syn_name);
end;

procedure Tform_main.OnRichEditorFontSizeClick(Sender: TObject);
var
  selected_size: string;
  edit_text: TRichMemo;
begin
  selected_size := TMenuItem(Sender).Caption;
  edit_text := self.GetActiveEditor() as TRichMemo;
  if edit_text = nil then
    exit;
  self.SetRichEditorFontSize(edit_text, StrToInt(selected_size));
end;

procedure Tform_main.InsertMarkdownElement(edit_text: TMemo; str_element: string);
begin
  edit_text.SelText := str_element;
end;

procedure Tform_main.SelectDefaultNotebook;
var
  notebook_id: integer;
  ini: TInifile;
  sql_query: TSQLQuery;
  vnode: TTreeNode;
begin
  notebook_id := 0;
  // 先读取配置文件是否有默认笔记本
  ini := TInifile.Create(ExtractFilePath(ParamStr(0)) + 'config.ini');
  try
    notebook_id := StrToInt(ini.ReadString('notebook', 'default_notebook', '0'));
  finally
    ini.Free;
  end;

  // 再读取数据库看看是首条记录；
  if notebook_id = 0 then
  begin
    sql_query := TSQLQuery.Create(nil);
    try
      sql_query.SQLConnection := self.sqlite3_connection;
      sql_query.SQL.Text := 'select id from notebook limit 1;';
      sql_query.Open;

      sql_query.First;
      while not sql_query.EOF do
      begin
        notebook_id := sql_query.FieldByName('id').AsInteger;
        break;
      end;
    finally
      sql_query.Close;
      sql_query.Free;
    end;
  end;

  // 查找notetook_id对应的节点
  vnode := self.FindNotebookNodeByNotebookId(notebook_id);
  if vnode <> nil then
  begin
    vnode.Selected := True;
  end;
end;

function Tform_main.FindNotebookNodeByNotebookId(notebook_id: integer): TTreeNode;
var
  vnode: TTreeNode;
  sub_node: TTreeNode;
  node_id: integer;
begin
  vnode := nil;
  vnode := tv_notebook.Items.GetFirstNode;
  if notebook_id = integer(vnode.Data) then
  begin
    Result := vnode;
    exit;
  end;
  while vnode <> nil do
  begin
    node_id := integer(vnode.Data);
    if node_id = notebook_id then
    begin
      Result := vnode;
      exit;
    end;
    sub_node := self.FindAllChildNodes(vnode, notebook_id);
    if sub_node <> nil then
    begin
      Result := sub_node;
      exit;
    end;
    vnode := vnode.GetNextSibling;
  end;
  Result := nil;
end;

function Tform_main.FindAllChildNodes(ANode: TTreeNode; notebook_id: integer): TTreeNode;
var
  vnode: TTreeNode;
begin
  vnode := ANode.GetFirstChild;
  while vnode <> nil do
  begin
    if notebook_id = integer(vnode.Data) then
    begin
      Result := vnode;
      exit;
    end;
    self.FindAllChildNodes(vnode, notebook_id);
    vnode := ANode.GetNextChild(vnode);
  end;
  Result := nil;
end;

end.
